使用长整数来存储32位指针会导致printf出错

pro*_*rsn 1 c x86 printf 32-bit long-long

用C指针搞乱一点,我遇到了一个相当奇怪的行为.
请考虑以下代码:

int 
main ()
{
   char charac = 'r';

   long long ptr = (long long) &charac;  // Stores the address of charac into a long long variable

   printf ("[ptr] points to %p containing the char %c\n", ptr, *(char*)ptr);

}
Run Code Online (Sandbox Code Playgroud)

在64位架构上

现在,当编译为64位目标体系结构(编译命令:)时gcc -Wall -Wextra -std=c11 -pedantic test.c -o test,一切都很好,执行给出

> ./test 
[ptr] points to 0x7fff3090ee47 containing the char r
Run Code Online (Sandbox Code Playgroud)

在32位架构上

但是,如果编译的目标是32位arch(带编译命令gcc -Wall -Wextra -std=c11 -pedantic -ggdb -m32 test.c -o test:),则执行会产生这种奇怪的结果:

> ./test     
[ptr] points to 0xff82d4f7 containing the char ?
Run Code Online (Sandbox Code Playgroud)

现在最奇怪的部分是如果我printf将前一个代码中的调用更改为printf ("[ptr] contains the char %c\n", *(char*)ptr);,则执行会给出正确的结果:

> ./test     
[ptr] contains the char r
Run Code Online (Sandbox Code Playgroud)

这个问题似乎只出现在32位arch上,我无法弄清楚printf调用更改导致执行行为不同的原因.

PS:可能值得一提的是,底层机器是x86 64位架构,但是使用了由该-m32选项触发的32位兼容模式gcc.

Ger*_*rdh 7

你基本上是在欺骗你的编译器.

您告诉printf您将指针作为格式字符串后面的第一个参数传递.但是你传递一个整数变量.

虽然这始终是未定义的行为,但只要预期类型和传递类型的大小相同,它就可能以某种方式工作.这是"未定义行为"中的"未定义".它也没有被定义为崩溃或立即显示不良结果.它可能只是等待从后面击中你的工作.

如果您的long long64位而指针只有32位,则堆栈的布局会被破坏,导致printf从错误的位置读取.

根据您的体系结构和工具,当您使用可变参数列表调用函数时,您很有可能堆栈如下所示:

+---------------+---------------+---------------+
| last fixed par| Par 1   type1 | Par 2   type2 |
|    x bytes    |    x bytes    |    x bytes    | 
+---------------+---------------+---------------+
Run Code Online (Sandbox Code Playgroud)

将未知参数压入堆栈,最后推送签名中的最后一个已知参数.(这里忽略其他已知参数)

然后该函数可以使用va_arg和朋友一起遍历参数列表.为此,函数必须知道传递了哪些类型的参数.该printf函数使用格式说明符来决定从堆栈中使用哪个参数.

现在谈到一切都取决于你说实话.

你告诉你的编译器:

+---------------+---------------+---------------+
| format  char* | Par 1   void* | Par 2     int |
|    4 bytes    |    4 bytes    |    4 bytes    | 
+---------------+---------------+---------------+
Run Code Online (Sandbox Code Playgroud)

对于第一个参数(%p),编译器需要4个字节,即a的大小void*.然后它需要另外4个字节(大小为a int)的参数2(%c).

(注意:最后一个参数打印为一个字符,即最后只使用1个字节.由于没有正确参数类型规范的函数调用的整数类型提升规则,参数存储为int堆栈.因此printf还必须int在这种情况下消耗a的字节.)

现在让我们来看看你的函数调用(你真正投入的内容printf):

+---------------+-------------------------------+---------------+
| format  char* |   Par 1           long long   | Par 2     int |
|    4 bytes    |            8 bytes            |    4 bytes    | 
+---------------+-------------------------------+---------------+
Run Code Online (Sandbox Code Playgroud)

您仍声称提供指针和每个4字节的整数参数.但是现在第一个参数带有额外的4个字节的长度,该printf功能仍然是未知的.正如您所说,该函数为指针读取4个字节.这可能与前4个字节一致,long long但不消耗剩余的4个字节.现在%c读取用于格式的接下来的4个字节,但我们仍在阅读你的后半部分.long long无论这可能是什么,它都不是你想要的.最后,当函数返回时,推送的整数仍未触及.

这就是为什么你不应该混淆奇怪的类型转换和错误类型的原因.

这也是您在编译期间应该查看警告的原因.

  • 如果你有一个没有指定参数类型的函数,那么在调用函数之前,任何像变量这样的整数都会被提升为`int`.这意味着对于`char`或`short`,使用与`int`相同的字节数.因此,该函数在处理该参数时必须获取完全相同的数字.当然,从堆栈中取出参数后,当使用`%c`时,只有1个字节用于打印. (2认同)