%p 格式说明符需要显式转换为 void* 对于 printf 中除 char* 之外的所有类型

Posted

技术标签:

【中文标题】%p 格式说明符需要显式转换为 void* 对于 printf 中除 char* 之外的所有类型【英文标题】:%p format specifier needs explicit cast to void* for all types but char* in printf 【发布时间】:2018-12-16 20:15:35 【问题描述】:

我在 Stack Overflow 中阅读了很多关于 C 语言中 %p 格式说明符用法的答案,但似乎没有人解释为什么除了 @ 之外的所有类型都需要显式转换为 void* 987654325@. 我当然知道这样一个事实,即转换到void* 或从void* 转换的要求与可变参数函数的使用有关(请参阅此answer 的第一条评论),而在其他情况下则不是强制性的。

这是一个例子:

int i;    
printf ("%p", &i);

产生关于类型不兼容的警告,&i 应转换为 void*(根据标准要求,请再次参阅 here)。

而这段代码编译顺利,没有任何关于类型转换的抱怨:

char * m = "Hello";    
printf ("%p", m);

char* 是如何从这个命令中“解脱”出来的?

PS:可能值得补充的是,我在 x86_64 架构上工作,因为指针类型大小取决于它,并且使用 gcc 作为 linux 上的编译器,带有 -W -Wall -std=c11 -pedantic 编译选项。

【问题讨论】:

您可以使用clang 5.* 进行检查,如果您将char *%p 放在一起,则会显示警告:warning: format specifies type 'void *' but the argument has type 'char *' @PaulAnkman 你完全正确!刚刚试了一下,并给出了关于void*char* 之间类型不兼容的经典讨厌警告。你怎么解释?正如其他答案所指出的,clang 是否没有实现关于相同表示/相同对齐类型的可互换性的 c 标准规范? 【参考方案1】:

char* 类型的参数不需要显式转换,因为char * 具有与void * 相同的表示和对齐要求。

引用C11,第 6.2.5 章

指向 void 的指针应具有与 指向字符类型的指针。 (48) [...]

和脚注 48)

相同的表示和对齐要求意味着作为函数的参数、函数的返回值和联合成员的可互换性。

【讨论】:

确实,这支持了这个说法:***.com/questions/43092033/… @programmersn 这不仅仅是关于显式转换。指针在内存中的表示也是一样的。 @programmersn 一些架构具有分段或分页地址空间。例如。 XC167 在 24 位地址空间内有 10 位页面 + 14 位偏移量。普通指针在单独的 16 位字中具有页和偏移量。出于某些目的,还可以使用线性地址格式。这些是不同的表示,而指针在两个版本中都使用 4 个字节。 @programmersn - 另一个例外是(曾经)具有字寻址内存的系统,其中char* 可能必须包含部分字指示符才能从更大的内存字中提取单个字符。 @programmersn:奇怪的东西存在;检查this related question 一些常见假设不再适用的“有趣”架构......【参考方案2】:

C11 标准 6.2.5/28 说:

指向 void 的指针应具有与 指向字符类型的指针。 48)

脚注 48 是:

相同的表示和对齐要求意味着作为函数参数的可互换性、函数返回值和联合成员。

但是 7.21.6.1(“fprintf 函数”)提到了 %p

参数应该是一个指向void的指针。


这显然是矛盾的。在我看来,一个合理的解释是说 6.2.5/28 的意图是 void *char * 实际上可以互换,因为函数参数的类型不对应于原型。 (即非原型函数的参数,或匹配可变参数函数原型的省略号)。

显然您使用的编译器也有类似的看法。

为了支持这一点,7.21.6.1 中的参数类型规范,如果从字面上理解而不考虑意图,那么在实践中必须忽略许多其他不一致(例如,它说 printf("%lx", -1); 很好 -已定义,但 printf("%u", 1); 是未定义的行为)。

【讨论】:

你是唯一一个提到标准的编译器解释。这也许就是为什么clang 确实抱怨所讨论的类型不兼容,而gcc 没有。 @programmersn,符合 C 实现不需要发出有关违反语言约束以外的任何内容的诊断消息,并且您描述的情况不包含任何约束违规。一个编译器发出诊断信息,而另一个编译器联合起来并不比这两个事实单独具有更重要的意义。【参考方案3】:

此要求的原因是 C 标准允许对指向不同类型的指针进行不同的表示,具有 2 个值得注意的约束:

指向voidcharunsigned char 的指针及其限定版本应具有相同的表示。 pointers to structures and unions must have the same representation。

因此在某些架构上,int *char * 可能具有不同的表示形式,例如不同的大小,并且它们可以以不同的方式传递给可变参数函数,从而导致 int i = 1; printf("%p", &i);int i = 1; printf("%p", (void*)&i); 的行为不同。

但是请注意,Posix 标准要求所有指针类型具有相同的大小和表示形式。因此,在 Posix 系统上,printf("%p", &i); 的行为应该符合预期。

【讨论】:

你的例子中ip的类型是什么?【参考方案4】:

来自 C 标准#6.2.5p28

指向 void 的指针应具有与指向字符的指针相同的表示和对齐要求 类型。48) 同样,指向兼容类型的合格或非合格版本的指针应具有相同的表示和对齐要求。所有指向结构类型的指针都应具有彼此相同的表示和对齐要求。所有指向联合类型的指针都应具有彼此相同的表示和对齐要求。指向其他类型的指针不需要具有相同的表示或对齐要求。 [强调我的]

【讨论】:

这正是我发布到 Sourav 的 Ghosh 答案的链接(但您可能没有看到)。 =) 是的,我没有,您的评论和我的回答可能相差几秒钟。

以上是关于%p 格式说明符需要显式转换为 void* 对于 printf 中除 char* 之外的所有类型的主要内容,如果未能解决你的问题,请参考以下文章

void及void指针介绍

C#编程(四十一)----------用户定义的数据类型转换

void指针的转换

malloc/free 的使用要点

从同步控制器方法到异步控制器方法的正确转换(使用void时)

C语言 NULL赋结构体指针变量