1

用 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);

}

在 64 位架构上

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

> ./test 
[ptr] points to 0x7fff3090ee47 containing the char r

在 32 位架构上

但是,如果编译的目标是 32 位拱门(使用编译命令 : gcc -Wall -Wextra -std=c11 -pedantic -ggdb -m32 test.c -o test),则执行会给出这个奇怪的结果:

> ./test     
[ptr] points to 0xff82d4f7 containing the char �

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

> ./test     
[ptr] contains the char r

这个问题似乎只出现在 32 位架构上,我无法弄清楚为什么printf调用更改会导致执行行为不同。

PS:可能值得一提的是,底层机器是x86 64位架构,但是使用.in-m32选项gcc

4

2 回答 2

7

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

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

尽管这始终是未定义的行为,但只要预期类型和传递的类型的大小相同,它就可能以某种方式起作用。这就是“未定义行为”中的“未定义”。它也未定义为崩溃或立即显示不良结果。它可能只是假装在工作,等待从后面击中你。

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

根据您的架构和工具,当您调用具有可变参数列表的函数时,您的堆栈很有可能看起来像这样:

+---------------+---------------+---------------+
| last fixed par| Par 1   type1 | Par 2   type2 |
|    x bytes    |    x bytes    |    x bytes    | 
+---------------+---------------+---------------+

未知参数被压入堆栈,最后压入签名中的最后一个已知参数。(其他已知参数在此忽略)

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

现在到了一切都取决于你说真话的地步。

你告诉你的编译器:

+---------------+---------------+---------------+
| format  char* | Par 1   void* | Par 2     int |
|    4 bytes    |    4 bytes    |    4 bytes    | 
+---------------+---------------+---------------+

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

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

现在让我们看看你的函数调用(你真正投入了什么printf):

+---------------+-------------------------------+---------------+
| format  char* |   Par 1           long long   | Par 2     int |
|    4 bytes    |            8 bytes            |    4 bytes    | 
+---------------+-------------------------------+---------------+

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

这就是为什么你不应该搞乱奇怪的类型转换和错误类型的原因。

这也是为什么您应该在编译期间查看警告的原因。

于 2018-08-22T09:29:20.110 回答
2

一个大问题:您为整数/指针恶作剧使用了错误的类型。该类型intptr_t是可以存储指针的整数类型。

那么,32 位架构出了什么问题呢?

该类型long long int(使用 gcc)是 64 位类型。但是,printf带有%p格式的命令期望接收 32 位指针,而不是 64 位指针。

对 printf 的调用将在调用堆栈上进行:(仅用于说明目的,细节可能不同)

pointer to format string
ptr (8 bytes)
*(char *)ptr (at least 1 byte, likely 4)

printf读取格式字符串,发现它应该接收一个 32 位指针和一个char. 然后它读取前 4 个字节ptr作为要读取的指针,接下来的 1-4 个字节作为要打印的字符。它甚至不知道堆栈上还有更多数据,即它应该打印的实际字符。

于 2018-08-22T09:43:46.127 回答