C 语言常见误解/指标/表示法与转型
指标(一)表示法和转型
编辑问:指标不就是内存的位置吗?不同型态的指标只有在位移(指标运算)时不一样,实际上连函式指标都只是内存中的位置不是吗?
答:并不是所有机器都使用“平面内存模型”(flat memory model),可以把内存当成一个巨大、连续的表格看待。除了标准(参考 N1256 6.2.5p27)规定的几种状况外,不同型态的指标可以长得完全不一样。最后,函式指标(function pointer)是另一世界的东西,可以跟物件指标完全没关系。并不是所有机器都把物件和程式码都摆在同一大块内存里面。
如果你测试发现一样,那只是你测试的实作用同一种方法表示所有指标,请参考你操作系统或机器的文件确认你的发现。世界上的确有实作用不同格式表示不同型态的指标,不可携的程式在那些机器上可能会出现严重的错误。(参考 clc FAQ 5.17 看实际例子)
问:所有指标都能转型成 void*
或是 char*
不是吗?
答:标准不保证函式指标也可以转型成 void* 或 char*.(参考 clc FAQ 4.13 和 N1256 6.3.2.3p1)
问:指标可以转型成 int
或是 long
不是吗?
答:就算不考虑函式指标,还是不保证成功,而且转过去的数字可能也无法直接读。实际的例子像是 AMD64 指令集下 Linux/Mac OS/Solaris/FreeBSD 等等(采用 LP64 资料模型)里面的 int
或是 Microsoft Windows 的 X64 版本(采用 LLP64 资料模型)里面的 int
和 long
都存不下 64 位元的指标。可携程式码不需要因为指标大小变了改写。 (参考标准 N1256 6.3.2.3p6)
问:把指标(直接)转型成 intptr_t
或 uintptr_t
总可以了吧?
答:同上。就算不考虑函式指标,还是不保证成功。
问:那到底要怎么写才能转型成整数存起来?
答:先转型成 void*
(注意函式指标可能不能转)然后再转型成 intptr_t
或 uintptr_t
.
intptr_t i = (intptr_t)(void*)p; // 假設 p 的型態是 int*
要用时先转回 void*
再转回原来指标。
int* pi = (int*)(void*)i;
这是万无一失,绝对可携的作法。唯一的问题是实作可能没有提供 intptr_t
. 在某些机器上指标也不用数字表示。(参考 N1256 7.18.1.4p1 和 clc FAQ 5.17 看实际例子)
问:如果两个指标比较(==
运算子)相等,(成功)转型成整数型态后比较也会相等吗?
答:没有保证,因此用此种方法来做 hash 之类的不可携。可转型回原本的指标型态再比较是否相等。(参考 comp.std.c 的讨论)
问:假如指标成功转型成另一个型态的指标,应该就可以用转型后的指标去存取物件吧?
答:否,转型成功不代表可以用来存取。标准对于什么型态的指标能存取什么型态的物件有严格规定(type-based aliasing rules)。例如 int 物件不能被指向 short 的指标存取,即使指标转型成功也不行。除了标准规定的例外外,编译器可以假设不同型态的存取指向不同物件,做超乎想像的最佳化,把运算顺序随意调动;如下列程式码:
int i = 0;
short *s = (short*) &i;
i = 0;
*s = 1;
在实际的机器上,最后 i
的值可能还是 0
. 如果想违反标准,请了解如何关掉相关的最佳化。(参考 N1256 6.5p7, Linus Torvalds 的抱怨,gcc 应该可用 -fno-strict-aliasing 关掉 和 N1520: Fixing the rules for type-based aliasing)
问:假设 p
是一个指标,那么 printf("%p", p)
就该印出 0x7fff12345678
之类的东西不是吗?
答:printf
系列函式的 %p
只能印指向 void
的指标,所以必须先转型成 void*
(注意函式指标可能不能转)。只要 p
不是函式指标就可以这样写:
printf("%p", (void*)p);
注意即使转型成功,指标要怎么印是实作决定,不一定要印成十六进制,也不一定要加上 0x。(参考 N1256 7.19.6.1p8)
问:我操作系统的文件说 %p
其实可以当作 %#x
或是 %#lx
. 这是不是代表其实可以用 %#x
或 %#lx
印出?
答:查操作系统文件值得鼓励,但如同问题所描述,这顶多只有在该操作系统上才对会。印指标还是要用 %p
或是用上述方法转成 intptr_t
再印出来。