【问题标题】:Why does printing out variables in C change their values?为什么在 C 中打印出变量会改变它们的值?
【发布时间】:2013-10-04 04:11:04
【问题描述】:

我只是用C写了一个简单的程序,如下所示:

#include <stdio.h>
void main()
{
  int *pointer;
  int number = 4;
  printf("number = %d\n",number);
  pointer = &number;
  printf("number = %d and pointer = %d\n",number,pointer);
  printf("pointer = %d and number = %d",pointer,number);
}

并在执行后打印输出如下:

number = 4 and pointer = 8724
pointer = 8724 and number = 9415

那么,为什么只有打印方向改变时才会改变值。我无法证明这条规则的合理性。

【问题讨论】:

  • 因为海森堡。
  • 您的代码和输出不匹配 - 第一个 printf() 似乎没有打印出来。确保显示您的实际代码和实际执行的输出。
  • 这是未定义的行为。为什么你们总是认为指针是int?我的意思是,怎么会?它不是。 显然不是。永远不要试图将其视为一个。这是未定义的行为。
  • @GrijeshChauhan 噢,真难过。很伤心。
  • @H2CO3 所说的重要一点是变量number 的实际值没有改变——它仍然是4。只是printf 打印不正确,因为它可能只得到了打印(可能是 64 位)指针的中途。

标签: c pointers


【解决方案1】:

您使用的是 64 位编译器吗?

int number = 4;
int *pointer = &number;
printf("number = %d and pointer = %d\n", number, pointer);
printf("pointer = %d and number = %d\n", pointer, number);

如果是这样,上面代码中的第一个 printf() 调用将一个 4 字节的 int 值和一个 8 字节的 int * 值压入堆栈,并告诉 printf() 打印两个 4 字节的数量.这些显然是 4 字节 int 和 8 字节地址的 4 字节。第二个printf() 调用将一个8 字节的int * 和一个4 字节的int 压入堆栈,并告诉printf() 再次打印两个4 字节的数量。不过,这一次,它们是分别打印的 8 字节 int * 值的两半。这严格地调用了未定义的行为,但这是一种似是而非的可能性,可以解释您看到的结果。

(潜伏着一些注意事项。我假设对于 64 位编译,CHAR_BIT == 8sizeof(int) == 4sizeof(int *) == 8;此外,实现在堆栈上传递参数,并且可能有一些相关的假设没有得到 C 标准的严格保证,但通常适用于 64 位编译器。)

你应该决定你要做什么。如果你想看两次4,那么:

int number = 4;
int *pointer = &number;
printf("number = %d and pointer = %d\n", number, *pointer);
printf("pointer = %d and number = %d\n", *pointer, number);

如果你想查看地址,那么你应该使用%p作为地址(并严格转换为void *):

int number = 4;
int *pointer = &number;
printf("number = %d and pointer = %p\n", number, (void *)pointer);
printf("pointer = %p and number = %d\n", (void *)pointer, number);

如果要控制指针的格式,则需要 C99 #include &lt;inttypes.h&gt; 和:

int number = 4;
int *pointer = &number;
printf("number = %d and pointer = 0x%.16" PRIXPTR "\n", number, (uintptr_t)pointer);
printf("pointer = 0x%.16" PRIXPTR " and number = %d\n", (uintptr_t)pointer, number);

示例代码

#include <stdio.h>
#include <inttypes.h>

int main(void)
{
    int number = 4;
    int *pointer = &number;
    printf("number = %d and pointer = %d\n", number, pointer);
    printf("pointer = %d and number = %d\n", pointer, number);

    printf("number = %d and pointer = %d\n", number, *pointer);
    printf("pointer = %d and number = %d\n", *pointer, number);

    printf("number = %d and pointer = %p\n", number, (void *)pointer);
    printf("pointer = %p and number = %d\n", (void *)pointer, number);

    printf("number = %d and pointer = 0x%.16" PRIXPTR "\n", number, (uintptr_t)pointer);
    printf("pointer = 0x%.16" PRIXPTR " and number = %d\n", (uintptr_t)pointer, number);

    return 0;
}

编译时出现警告

Mac OS X 10.8.5 上的 GCC 4.8.1。 clang 也给出了类似的警告。

gcc -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition   it.c -o it
it.c: In function ‘main’:
it.c:8:5: warning: format ‘%d’ expects argument of type ‘int’, but argument 3 has type ‘int *’ [-Wformat=]
     printf("number = %d and pointer = %d\n", number, pointer);
     ^
it.c:9:5: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘int *’ [-Wformat=]
     printf("pointer = %d and number = %d\n", pointer, number);
     ^

示例输出

number = 4 and pointer = 1417245952
pointer = 1417245952 and number = 4
number = 4 and pointer = 4
pointer = 4 and number = 4
number = 4 and pointer = 0x7fff54797500
pointer = 0x7fff54797500 and number = 4
number = 4 and pointer = 0x00007FFF54797500
pointer = 0x00007FFF54797500 and number = 4

有趣的是,系统设法在前两行输出中两次打印4。我还没有研究过汇编器来看看它是如何工作的——但是调用“未定义行为”的好处之一是你不能抱怨结果;任何结果都是正确的,因为所需的行为是未定义的。

【讨论】:

  • PRIXPTR 是什么?我不知道。是像PRIu32 这样的宏吗?
  • @GrijeshChauhan 是的。
  • @GrijeshChauhan:PRIXPTR 是一个宏,它为要以大写十六进制打印的uintptr_t 定义正确的转换说明符。它与PRIu32 相关,因为它来自&lt;inttypes.h&gt;
  • @JonathanLeffler 谢谢!很有意思。您使用0x%.16" PRIXPTR 的另一个问题?这意味着您要以160x 为前缀的十六进制数字打印是32 位还是64 位机器。
  • @GrijeshChauhan:我的整个答案都基于“64 位”,但是是的,0x%.16" PRIXPTR 格式确实意味着 16 个十六进制数字,即使 64 位地址仅使用 12 个非零我的 Mac 上的十六进制数字(如果它以 32 位编译,则意味着 8 个前导零)。查看最近添加的示例输出。
【解决方案2】:

这可能是由于未定义的行为!当您为地址传递不正确的格式字符串时。

使用%p 打印地址并将其排版为void*

printf("number = %d and pointer = %p\n",number, (void*)pointer);
printf("pointer = %p and number = %d", (void*)pointer, number);

printf 的部分引用的 fprintf 函数用于格式字符串段落:

如果转换规范无效,则行为未定义。[...]

文字取自@Shafik Yaghmour' answer

【讨论】:

    【解决方案3】:

    使用*pointer 代替pointer

    Pointer 打印地址,*pointer 打印值。

    printf("number = %d and pointer = %d\n",number, *pointer);  
                                                    ^ 
    printf("pointer = %d and number = %d",*pointer,number);
                                          ^
    

    【讨论】:

      【解决方案4】:

      看来您使用的平台int 的大小和指针的大小不同,或者指针和整数的参数传递不太可能不同。您尝试打印的类型 (%d) 与您传递的类型(第一种情况下为 int/pointer,第二种情况下为指针/int)之间存在不匹配。

      【讨论】:

        猜你喜欢
        • 2011-11-25
        • 2021-03-10
        • 1970-01-01
        • 2017-02-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多