当printf的是一个变量的地址,为什么用void *? [英] When printf is an address of a variable, why use void*?

查看:125
本文介绍了当printf的是一个变量的地址,为什么用void *?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我看到一些使用(无效*)的printf()

如果我想打印一个变量的地址,我可以做到这一点是这样的:

  int类型的= 19;
的printf(%d个,&安培; A);


  1. 我觉得,&放大器;一个 A 的地址这只是一个整数,权

  2. 很多文章我读了使用这样的:

     的printf(%P(无效*)及一);



  1. 这是什么%P 代表什么? (指针?)

  2. 为什么要用(无效*)?我不能使用(INT)及?一个而不是


解决方案

指针不是数字。他们往往在内部重新presented这种方式,但它们在概念上是不同的。

无效* 被设计为一个通用的指针类型。任何指针值(比函数指针除外)可以被转换成无效* ,然后再返回而不会丢失信息。这通常意味着无效* 至少是一样大的其他指针类型。

的printf 取值%P格式的需要的类型<的参数code>无效* 。这就是为什么一个为int * 应在这方面转换为无效* 。 (有没有隐式转换,因为它是一个可变参数函数;没有声明参数,所以编译器不知道如何将它转换)

马虎的实践,如印刷指针与%D,或穿行为int * 的printf %p格式,是事情,你也许可以蒙混过关的最新系统,但他们提供的code非便携。 (请注意,这是常见的64位系统无效* INT 是不同的大小,所以用印刷指针%D是的真正的不可移植,而不仅仅是理论上的。)

顺便说一下,%P输出格式为实现定义。十六进制是常见的,(大写或小写,带或不带前导0X0X) ,但它不是唯一的可能性。所有你可以指望的是,假设一个合理的实现,这将是重新present合理的方式在人类可读的形式指针值(和 scanf函数就会明白的输出的printf )。

你读这篇文章是完全正确的。正确的方法来打印为int * 值为

 的printf(%P(无效*)及一);

不要采取懒惰的出路;它不是在所有很难得到它的权利。

推荐阅读:第4节的 comp.lang.c常见问题解答。 (进一步建议阅读:所有其他部分

编辑:

在回应奥尔科特的问题:


  

还有一件事我不太明白。 int类型的= 10;为int * p =&放大器;一个; ,因此P的值是一个纪念品的地址,对不对?如果正确,那么p值范围从0到2 ^ 32-1(如果CPU是32位的),和一个整数是32位OS 4个字节的,对不对?那么什么是普的价值和整数之间的区别?普的值可以走出去的范围?


不同的是,他们是不同类型的。

假设一个系统上 INT 为int * 无效* 浮动都是32位(这是典型的当前32位系统)。难道一个事实,即浮动是32位意味着它的范围是0到2 32 -1?或-2 31 2 31 -1?当然不是;浮动的(假设重新IEEE presentation)的范围大约是-3.40282e + 38 + 3.40282e + 38,与整个范围内广泛变化的分辨率,再加上像负零异国值,subnormalized数,非正规化数,无穷大和NaN(不是非数字)。 INT 浮动都是32位的,而你的可以的利用由32位浮动对象,并把它作为一个 INT 重新presentation,但结果不会有任何直接的关系在浮动的值。的 INT ,例如,有特定的含义。第二低位;它有助于0的值,如果它是0,和2的值,如果它是1;一个浮动的对应位都有其意义,但它是完全不同的(它有助于依赖于指数的值的值)。

与指针的情况颇为相似。指针值有一个意思:它的一些对象的地址(或任何其他几件事情,但我们会设置预留了)。在大多数当前的系统,除preting指针对象的位就好像它是一个整数,给你的东西,使机器层次感。但是语言本身并不能保证,甚至暗示,这是这种情况。

指针不是数字。

一个具体的例子:几年前,我碰到一些code,它试图通过转换为整数来计算两个地址相差的字节数跑去。它是这样的:

 无符号字符* P0;
无符号字符* P1;
长差=(无符号长)P1 - (无符号长)P0;

如果您认为指针只是数字,在一个单片线性地址空间重新presenting地址,那么这个code是有道理的。但这种假设不是由语言支持。而事实上,有哪些是code旨在运行(克雷T90)上,它根本就无法工作的系统。在T90已经指向64位字的64位指针。字节指针被存储一个在指针对象的3高阶位偏移软件合成。以上述方式减去两个指针,如果他们两人都0偏移,会给你的的,不是字节数,地址之间。如果他们有非0的偏移量,它会给你毫无意义的垃圾。 (转换从指针到一个整数将刚才复制的比特;它的可能的所做的工作给你一个有意义的字节索引,但它没有)

解决的办法很简单:掉落的演员,并使用指针运算:

 长差= P1  -  P0;

其它寻址方案是可能的。例如,一个地址可能包含一个描述符(也许间接)引用的存储器的块的,加上该块内的偏移。

可以假定地址只是号码,该​​地址空间是线性的和单片,即所有的指针的大小相同,并且具有相同重presentation,指示器可以安全地转换为 INT ,或,并再次没有信息丢失。而且你写基于这些假设code可能会在最新的系统上运行。但它完全有可能是一些未来的系统将再次使用一个不同的内存模型,和你的code将中断。

如果你避免作出任何假设,超越了语言实际上可以保证,你的code会更面向未来。即使离开可移植性问题不谈,它可能会更干净。

I saw some usage of (void*) in printf().

If I want to print a variable's address, can I do it like this:

int a = 19;
printf("%d", &a);

  1. I think, &a is a's address which is just an integer, right?
  2. Many articles I read use something like this:

    printf("%p", (void*)&a);
    

  1. What does %p stand for? (A pointer?)
  2. Why use (void*)? Can't I use (int)&a instead?

解决方案

Pointers are not numbers. They are often internally represented that way, but they are conceptually distinct.

void* is designed to be a generic pointer type. Any pointer value (other than a function pointer) may be converted to void* and back again without loss of information. This typically means that void* is at least as big as other pointer types.

printfs "%p" format requires an argument of type void*. That's why an int* should be cast to void* in that context. (There's no implicit conversion because it's a variadic function; there's no declared parameter, so the compiler doesn't know what to convert it to.)

Sloppy practices like printing pointers with "%d", or passing an int* to printf with a "%p" format, are things that you can probably get away with on most current systems, but they render your code non-portable. (Note that it's common on 64-bit systems for void* and int to be different sizes, so printing pointers with %d" is really non-portable, not just theoretically.)

Incidentally, the output format for "%p" is implementation-defined. Hexadecimal is common, (in upper or lower case, with or without a leading "0x" or "0X"), but it's not the only possibility. All you can count on is that, assuming a reasonable implementation, it will be a reasonable way to represent a pointer value in human-readable form (and that scanf will understand the output of printf).

The article you read is entirely correct. The correct way to print an int* value is

printf("%p", (void*)&a);

Don't take the lazy way out; it's not at all difficult to get it right.

Suggested reading: Section 4 of the comp.lang.c FAQ. (Further suggested reading: All the other sections.

EDIT:

In response to Alcott's question:

There is still one thing I don't quite understand. int a = 10; int *p = &a;, so p's value is a's address in mem, right? If right, then p's value will range from 0 to 2^32-1 (if cpu is 32-bit), and an integer is 4-byte on 32-bit OS, right? then What's the difference between the p's value and an integer? Can p's value go out of the range?

The difference is that they're of different types.

Assume a system on which int, int*, void*, and float are all 32 bits (this is typical for current 32-bit systems). Does the fact that float is 32 bits imply that its range is 0 to 232-1? Or -231 to 231-1? Certainly not; the range of float (assuming IEEE representation) is approximately -3.40282e+38 to +3.40282e+38, with widely varying resolution across the range, plus exotic values like negative zero, subnormalized numbers, denormalized numbers, infinities, and NaNs (Not-a-Number). int and float are both 32 bits, and you can take the 32 bits of a float object and treat it as an int representation, but the result won't have any straightforward relationship to the value of the float. The second low-order bit of an int, for example, has a specific meaning; it contributes 0 to the value if it's 0, and 2 to the value if it's 1; the corresponding bit of a float has a meaning, but it's quite different (it contributes a value that depends on the value of the exponent).

The situation with pointers is quite similar. A pointer value has a meaning: it's the address of some object (or any of several other things, but we'll set that aside for now). On most current systems, interpreting the bits of a pointer object as if it were an integer gives you something that makes sense on the machine level. But the language itself does not guarantee, or even hint, that that's the case.

Pointers are not numbers.

A concrete example: some years ago, I ran across some code that tried to compute the difference in bytes between two addresses by casting to integers. It was something like this:

unsigned char *p0;
unsigned char *p1;
long difference = (unsigned long)p1 - (unsigned long)p0;

If you assume that pointers are just numbers, representing addresses in a linear monolithic address space, then this code makes sense. But that assumption is not supported by the language. And in fact, there was a system on which that code was intended to run (the Cray T90) on which it simply would not have worked. The T90 had 64-bit pointers pointing to 64-bit words. Byte pointers were synthesized in software by storing an offset in the 3 high-order bits of a pointer object. Subtracting two pointers in the above manner, if they both had 0 offsets, would give you the number of words, not bytes, between the addresses. And if they had non-0 offsets, it would give you meaningless garbage. (Conversion from a pointer to an integer would just copy the bits; it could have done the work to give you a meaningful byte index, but it didn't.)

The solution was simple: drop the casts and use pointer arithmetic:

long difference = p1 - p0;

Other addressing schemes are possible. For example, an address might consist of a descriptor that (perhaps indirectly) references a block of memory, plus an offset within that block.

You can assume that addresses are just numbers, that the address space is linear and monolithic, that all pointers are the same size and have the same representation, that a pointer can be safely converted to int, or to long, and back again without loss of information. And the code you write based on those assumptions will probably work on most current systems. But it's entirely possible that some future systems will again use a different memory model, and your code will break.

If you avoid making any assumptions beyond what the language actually guarantees, your code will be far more future-proof. And even leaving portability issues aside, it will probably be cleaner.

这篇关于当printf的是一个变量的地址,为什么用void *?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆