当printf是变量的地址时,为什么要使用void *?

我在printf()看到了(void*)一些用法。

如果我想打印变量的地址,我可以这样做:

 int a = 19; printf("%d", &a); 
  1. 我想, &aa只是一个整数的地址,对吧?
  2. 我读过的很多文章都是这样的:

     printf("%p", (void*)&a); 
  1. %p代表什么? (一个指针?)
  2. 为什么要使用(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的值可以超出范围吗?

不同之处在于它们的类型不同。

假设一个系统,其中intint*void*float都是32位(这是当前32位系统的典型情况)。 float是32位的事实是否意味着它的范围是0到2 32 -1? 或-2 31到2 31 -1? 当然不是; 浮点范围(假设IEEE表示)约为-3.40282e + 38到+ 3.40282e + 38,在整个范围内具有广泛变化的分辨率,加上外来值,如负零,次标准化数,非规范化数,无穷大和NaN(非-一个号码)。 intfloat都是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_tuintptr_t 。 他们在stdint.h 。 见http://en.wikipedia.org/wiki/Stdint.h#Integers_wide_enough_to_hold_pointers

在C中, void *是一个未键入的指针。 void并不意味着无效……它意味着什么。 因此,转换为void *将与在另一种语言中转换为“指针”相同。

使用(int *)&a应该工作……但是说(void *)的风格点是 – 我不关心类型 – 只是它是一个指针。

注意:C的实现可能导致此构造失败并仍满足标准的要求。 我不知道任何这样的实现,但它是可能的。