当printf是变量的地址时,为什么要使用void *?
我在printf()
看到了(void*)
一些用法。
如果我想打印变量的地址,我可以这样做:
int a = 19; printf("%d", &a);
- 我想,
&a
是a
只是一个整数的地址,对吧? -
我读过的很多文章都是这样的:
printf("%p", (void*)&a);
-
%p
代表什么? (一个指针?) - 为什么要使用
(void*)
? 我不能用(int)&a
代替吗?
指针不是数字。 它们通常以内部方式表示,但它们在概念上是截然不同的。
void*
被设计为通用指针类型。 任何指针值(函数指针除外)都可以转换为void*
并再次返回而不会丢失信息。 这通常意味着void*
至少与其他指针类型一样大。
printf
s "%p"
格式需要 void*
类型的参数。 这就是为什么int*
应该在该上下文中转换为void*
。 (没有隐式转换,因为它是一个可变参数函数;没有声明的参数,所以编译器不知道将它转换为什么。)
像"%d"
打印指针,或者将int*
传递给带有"%p"
格式的printf
这样的邋practice实践,你可以在大多数当前系统中使用它们,但它们会使你的代码不可移植。 (请注意,在64位系统中, void*
和int
的常见大小不同,因此使用%d"
打印指针实际上是不可移植的,不仅仅是理论上的。)
顺便提一下, "%p"
的输出格式是实现定义的。 hex是常见的(大写或小写,有或没有前导"0x"
或"0X"
),但它不是唯一的可能性。 所有你可以依赖的是,假设一个合理的实现,它将是一种以人类可读的forms表示指针值的合理方式(并且scanf
将理解printf
的输出)。
你读的文章是完全正确的。 打印int*
值的正确方法是
printf("%p", (void*)&a);
不要采取懒惰的方式; 要做到这一点并不困难。
建议阅读: comp.lang.c FAQ的第4部分。 (进一步建议阅读:所有其他部分。
编辑:
针对Alcott的问题:
还有一件事我不太明白。
int a = 10; int *p = &a;
,所以p的值是mem中的地址,对吗? 如果正确,那么p的值将在0到2 ^ 32-1之间(如果cpu是32位),并且在32位操作系统上整数是4字节,对吧? 那么p的值和整数之间有什么区别? p的值可以超出范围吗?
不同之处在于它们的类型不同。
假设一个系统,其中int
, int*
, void*
和float
都是32位(这是当前32位系统的典型情况)。 float
是32位的事实是否意味着它的范围是0到2 32 -1? 或-2 31到2 31 -1? 当然不是; 浮点范围(假设IEEE表示)约为-3.40282e + 38到+ 3.40282e + 38,在整个范围内具有广泛变化的分辨率,加上外来值,如负零,次标准化数,非规范化数,无穷大和NaN(非-一个号码)。 int
和float
都是32位,你可以取float
对象的32位并将其视为int
表示,但结果与float
的值没有任何直接的关系。 例如, int
的第二个低位具有特定含义; 如果值为0则为0提供值,如果值为1则为2; float
的相应位有一个含义,但它有很大不同(它提供的值取决于指数的值)。
指针的情况非常相似。 指针值有一个含义:它是某个对象的地址(或其他几个东西的任何一个,但我们暂时将它放在一边)。 在大多数当前系统中,将指针对象的位解释为整数,可以为您提供在机器级别上有意义的东西。 但语言本身并不能保证甚至暗示这种情况。
指针不是数字。
一个具体的例子:几年前,我遇到了一些代码,试图通过转换为整数来计算两个地址之间的字节差异。 它是这样的:
unsigned char *p0; unsigned char *p1; long difference = (unsigned long)p1 - (unsigned long)p0;
如果您假设指针只是数字,表示线性单片地址空间中的地址,则此代码是有意义的。 但该语言不支持该假设。 事实上,有一个系统可以运行该代码(Cray T90),它根本就没有用。 T90有64位指针指向64位字。 通过在指针对象的3个高位中存储偏移量,在软件中合成字节指针。 以上述方式减去两个指针,如果它们都有0个偏移量,则会给出地址之间的字数而不是字节数。 如果他们有非0偏移量,它会给你毫无意义的垃圾。 (从指针到整数的转换只会复制这些位;它可以完成工作以给你一个有意义的字节索引,但它没有。)
解决方案很简单:删除强制转换并使用指针算法:
long difference = p1 - p0;
其他寻址方案也是可能的。 例如,地址可能包含一个描述符(可能是间接地)引用一块内存,加上该块内的偏移量。
您可以假设地址只是数字,地址空间是线性的和单一的,所有指针都是相同的大小并具有相同的表示,指针可以安全地转换为int
,或者long
,然后再返回而不会丢失信息。 您根据这些假设编写的代码可能适用于大多数当前系统。 但是,未来的某些系统完全有可能再次使用不同的内存模型,并且您的代码将会中断。
如果您避免做出超出语言实际保证的任何假设,您的代码将更加面向未来。 即使不考虑可移植性问题,它也可能更清洁。
这里有如此多的精神错乱……
如果您只想打印出指针的表示, %p
通常是正确的格式说明符。 永远不要使用%d
。
int
的长度和指针的长度( void*
或其他) 没有关系 。 i386上的大多数数据模型碰巧都有32位int
和32位指针 – 其他平台,包括x86-64,都不一样! (这在历史上也被称为“世界上所有的VAX综合症”。) http://en.wikipedia.org/wiki/64-bit#64-bit_data_models
如果由于某种原因想要在整数变量中保存内存地址,请使用正确的类型! intptr_t
和uintptr_t
。 他们在stdint.h
。 见http://en.wikipedia.org/wiki/Stdint.h#Integers_wide_enough_to_hold_pointers
在C中, void *
是一个未键入的指针。 void
并不意味着无效……它意味着什么。 因此,转换为void *
将与在另一种语言中转换为“指针”相同。
使用(int *)&a
应该工作……但是说(void *)
的风格点是 – 我不关心类型 – 只是它是一个指针。
注意:C的实现可能导致此构造失败并仍满足标准的要求。 我不知道任何这样的实现,但它是可能的。