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位目标体系结构(编译命令:)时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位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.
你基本上是在欺骗你的编译器.
您告诉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无论这可能是什么,它都不是你想要的.最后,当函数返回时,推送的整数仍未触及.
这就是为什么你不应该混淆奇怪的类型转换和错误类型的原因.
这也是您在编译期间应该查看警告的原因.