C 语言中能否用 %d 正确打印指针地址?
不能用 %d 正确、可移植地打印指针。%d 要求对应实参的类型为 int,这里传入的却是 int *,类型不匹配会导致未定义行为。打印对象指针应使用 %p,并将指针转换为 void *。
题目中贴出的代码本身还有语法错误,需要与指针输出问题分开讨论。
原代码有哪些问题?
原代码中的这两行不符合 C 语法:
int variable var = 8;
int* ptr = &var
第一行应写成 int var = 8;,第二行末尾缺少分号。如果考试代码与这里贴出的完全一致,编译器应当给出诊断,不能按正常程序推导输出。
如果这些只是回忆或抄写时产生的错误,修正声明后,剩下的问题是:
int var = 8;
int *ptr = &var;
printf("%d, %d", var, ptr);
第一个 %d 与 var 匹配,第二个 %d 与 ptr 不匹配。由于这次调用存在未定义行为,不能保证整个调用会输出什么,也不能保证一定先输出 8。
为什么编译通过也不代表写法正确?
printf 是可变参数函数。格式字符串告诉它应按什么类型读取后续参数,但不会自动把指针转换成指定的整数类型。
下面的调用不会因为写了 %d,就自动把指针转换为 int:
printf("%d", ptr);
编译器可能检测到格式不匹配并发出警告,但不一定因此阻止编译。即使生成了可执行文件,运行结果仍没有语言层面的保证。
某次运行即使得到了看似合理的整数,也不能说明它是地址的正确十进制表示。指针和 int 在某个平台上即使恰好宽度相同,格式不匹配的问题仍然存在。
正确打印地址的方法
使用 %p,并将指针显式转换为 void *:
#include <stdio.h>
int main(void)
{
int var = 8;
int *ptr = &var;
printf("%d, %p\n", var, (void *)ptr);
return 0;
}
%p 要求对应参数是 void *。指针作为可变参数传入时,显式转换可以满足这个类型要求。
地址的具体输出形式由实现决定。常见实现使用带 0x 前缀的十六进制形式,但 C 标准不保证所有实现都采用这种格式。
内存地址必须是十六进制吗?
不必。十进制和十六进制是数字的表示方式,地址本身没有必须使用的书写进制。十六进制常用于显示地址,因为它与二进制位的对应关系比较直观。
如果实现提供 uintptr_t,可以用下面的代码比较同一个指针转换所得整数的十进制和十六进制表示:
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
int main(void)
{
int var = 8;
uintptr_t address = (uintptr_t)(void *)&var;
printf("decimal: %" PRIuPTR "\n", address);
printf("hex: %" PRIxPTR "\n", address);
return 0;
}
uintptr_t 是可选的无符号整数类型。如果实现提供该类型,就支持把有效的 void * 转成 uintptr_t,再转回后与原指针比较相等。指针转换所得整数的具体值由实现决定,不应把它直接理解为物理内存地址。
两次输出使用同一个 address,因此是同一个整数的两种进制表示。PRIuPTR 和 PRIxPTR 分别提供与该类型匹配的十进制、十六进制格式。
如果之前的实验直接把指针传给 %d、%x,结果不能用来判断两种进制是否对应;如果比较的是不同次运行,变量地址也可能发生变化。
考试答案应如何理解?
“8, var 的内存地址”可能表达了出题者想考查的指针含义,但使用 %d 输出指针时,C 语言不保证得到这个结果。
原样代码存在语法错误。修正语法后,printf("%d, %d", var, ptr) 仍有未定义行为。正确打印对象指针,应使用 %p 并传入 (void *)ptr。
备注:内容仅供参考。
