C 语言常见误解/指标/表示法与转型

指标(一)表示法和转型

编辑

问:指标不就是记忆体的位置吗?不同型态的指标只有在位移(指标运算)时不一样,实际上连函式指标都只是记忆体中的位置不是吗?

答:并不是所有机器都使用“平面记忆体模型”(flat memory model),可以把记忆体当成一个巨大、连续的表格看待。除了标准(参考 N1256 6.2.5p27)规定的几种状况外,不同型态的指标可以长得完全不一样。最后,函式指标(function pointer)是另一世界的东西,可以跟物件指标完全没关系。并不是所有机器都把物件和程式码都摆在同一大块记忆体里面。

如果你测试发现一样,那只是你测试的实作用同一种方法表示所有指标,请参考你操作系统或机器的文件确认你的发现。世界上的确有实作用不同格式表示不同型态的指标,不可携的程式在那些机器上可能会出现严重的错误。(参考 clc FAQ 5.17 看实际例子)


问:所有指标都能转型成 void* 或是 char* 不是吗?

答:标准不保证函式指标也可以转型成 void* 或 char*.(参考 clc FAQ 4.13N1256 6.3.2.3p1)


问:指标可以转型成 int 或是 long 不是吗?

答:就算不考虑函式指标,还是不保证成功,而且转过去的数字可能也无法直接读。实际的例子像是 AMD64 指令集下 Linux/Mac OS/Solaris/FreeBSD 等等(采用 LP64 资料模型)里面的 int 或是 Microsoft Windows 的 X64 版本(采用 LLP64 资料模型)里面的 intlong 都存不下 64 位元的指标。可携程式码不需要因为指标大小变了改写。 (参考标准 N1256 6.3.2.3p6)


问:把指标(直接)转型成 intptr_tuintptr_t 总可以了吧?

答:同上。就算不考虑函式指标,还是不保证成功。


问:那到底要怎么写才能转型成整数存起来?

答:先转型成 void*(注意函式指标可能不能转)然后再转型成 intptr_tuintptr_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 再印出来。