C 代码的未知输出

Unknown Output of C code

下面的代码如何给出2036、2036、2036的答案。

以下 C 代码的输出是什么?假设 x 的地址是 2000(十进制),一个整数需要四个字节的内存。

int main()
{ 
   unsigned int x[4][3] = {{1, 2, 3}, {4, 5, 6}, 
                           {7, 8, 9}, {10, 11, 12}};
   printf("%u, %u, %u", x+3, *(x+3), *(x+2)+3);
}
   2000  4   8  12  16  20  24  28   32  36  40  44  48
     +---+---+---+---+---+---+---+---+---+---+---+---+
     | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10| 11| 12|
     +---+---+---+---+---+---+---+---+---+---+---+---+
   x {                                                } size = 48
      {   x[0]  } {   x[1]  } {   x[2]  } {   x[3]  }   size = 12 * 4

x = 2000
x + 3 = &x[3] = 2000 + 3 * ( 3 * 4) = 2036
                       ^    ^-----^ size of int[3]
                      index
*(x+2) + 3 = &x[2] + 3 off = 2000 + 2 * (3 * 4) + 3 * 4 = 2036

尽管 xx[0] 的地址相同,但类型(因此大小)不同。


您应该在 main 函数的末尾添加 return 0。此外,您的打印语句应如下所示:

printf("%p, %p, %p\n", (void *)(x+3), (void *)*(x+3), (void *)(*(x+2)+3));
/*      ^^  ^^  ^^^^   ^^^^^^^^^   ^  ^^^^^^^^        ^^^^^^^^^        ^ */

对于定义明确的行为。

正如您刚刚发现的那样,如果您不 post 精心编写代码,人们可能会对 SO 有点苛刻。要打印地址,请使用 %p 或来自 <inttypes.h> 的宏,例如 PRIXPTR 并强制转换为 uintptr_t.

仅使用 %p,您的代码可能应该是:

#include <stdio.h>

int main(void)
{ 
    unsigned int x[4][3] =
    {
        {  1,  2,  3 }, {  4,  5,  6 }, 
        {  7,  8,  9 }, { 10, 11, 12 }
    };
    printf("%p\n", (void *)x);
    printf("%p, %p, %p\n", (void *)(x+3), (void *)(*(x+3)), (void *)(*(x+2)+3));
    return 0;
}

现在您已经定义了行为,尽管这些值不像 2000 和 2036 那样方便。

但是,x+3是数组开始后第4个数组的地址int,所以假设sizeof(int) == 4如上所说,也就是开始后36个字节x — 2036 如果 x 是 2000.

*(x+3)是第四个数组的起始地址;在相同的假设下为 2036。

*(x+2)+3 将 3 添加到 3 int 的第三个数组的地址,然后再向其添加 3。如果 x 是 2000,那么它也会在 x 开始后 36 个字节结束,或者在 2036 结束。

在我的 64 位机器上,输出为:

0x7fff5800a440
0x7fff5800a464, 0x7fff5800a464, 0x7fff5800a464

十六进制 0x24 当然是十进制的 36。

编辑:正如 Jonathan Leffler 指出的那样,我应该在打印 %p

之前转换为 (void*)

也许这会阐明内存寻址方面的情况:

#include <stdio.h>

int main() {
    unsigned int x[4][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}};

    // Output: Address, 1
    printf("%p, %u\n", (void*)(x),      **x);

    // Output: Address + 12, 4
    printf("%p, %u\n", (void*)(x+1),    **(x+1));

    // Output: Address + 24, 7
    printf("%p, %u\n", (void*)(x+2),    **(x+2));

    // Output: Address + 4, 2
    printf("%p, %u\n", (void*)((*x)+1), *(*(x)+1));

    return 0;
}
  • **x 应该用来得到 x[0][0]
  • **(x+1) 应该用来得到 x[1][0]
  • **(x+2) 应该用来得到 x[2][0]
  • *(*(x)+1) 应该用来得到 x[0][1]

为了更加清楚:

#include <stdio.h>

int main() {
    unsigned int x[4][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}};

    printf("%p, %u\n", (void*)(x),      **x);       // Output: Address, 1
    printf("%u\n",                      x[0][0]);   // Output: 1

    printf("%p, %u\n", (void*)(x+1),    **(x+1));   // Output: Address + 12, 4
    printf("%u\n",                      x[1][0]);   // Output: 4

    printf("%p, %u\n", (void*)(x+2),    **(x+2));   // Output: Address + 24, 7
    printf("%u\n",                      x[2][0]);   // Output: 7

    printf("%p, %u\n", (void*)((*x)+1), *(*(x)+1)); // Output: Address + 4, 2
    printf("%u\n",                      x[0][1]);   // Output: 2

    return 0;
}