在 C 语言中,为什么我可以看到一个值写在不同变量的数组末尾之后?

In C, why can I see a value written past the end of an array in a different variable?

这些天我用业余时间做一些测试、有趣的事情和实现一些简单的算法、数据结构等各种东西,以我个人对 C 的兴趣....

但是,我最终发现了一些对我来说很有趣的东西。直到现在我也不知道为什么会出现这样的结果..

max_arr_count_index 根据 arr[5] 值分配,该值超过数组末尾 +1.

有人能给我解释一下吗?我知道不应该。我将值分配给数组的过去一个索引(这里,问题案例中的 arr[5] = 30) 而且它不安全,它是标准定义的未定义行为

我不会在真实领域做同样的事情,但是,我只是想在这里了解更多。

LLVM 和 GCC 给了我相同的结果。

代码和结果如下:

[没有问题的情况:我没有分配超过索引末尾的值]

#include <stdio.h>

int arr[] = {11,33,55,77,88};
int max_arr_count_index = (sizeof(arr) / sizeof(arr[0]));

// print all
void print_all_arr(int* arr)
{
    // just print all arr datas regarding index.
    for(int i = 0; i < max_arr_count_index; i++) {
        printf("arr[%d] = %d \n", i, arr[i]);
    }
}

int main(int argc, const char * argv[]) {
    // insert code here...
    printf("[before]max_arr_count_index : %d\n", max_arr_count_index);
    printf("[before]The original array elements are :\n");
    print_all_arr(arr);
    arr[0] = 1;
    arr[1] = 2;
    arr[2] = 3;
    arr[3] = 4;
    arr[4] = 5;
    // arr[5] = 1000;
    printf("[after]max_arr_count_index : %d\n", max_arr_count_index);
    printf("[after]The array elements after :\n");

    print_all_arr(arr);

    return 0;
}

没问题结果如下:

[before]max_arr_count_index : 5
[before]The original array elements are :
arr[0] = 11 
arr[1] = 33 
arr[2] = 55 
arr[3] = 77 
arr[4] = 88 
[after]max_arr_count_index : 5
[after]The array elements after :
arr[0] = 1 
arr[1] = 2 
arr[2] = 3 
arr[3] = 4 
arr[4] = 5 
Program ended with exit code: 0

[问题案例:我分配了超过索引末尾的值]

#include <stdio.h>

int arr[] = {11,33,55,77,88};
int max_arr_count_index = (sizeof(arr) / sizeof(arr[0]));

// print all
void print_all_arr(int* arr)
{
    // just print all arr datas regarding index.
    for(int i = 0; i < max_arr_count_index; i++) {
        printf("arr[%d] = %d \n", i, arr[i]);
    }
}

int main(int argc, const char * argv[]) {
    // insert code here...
    printf("[before]max_arr_count_index : %d\n", max_arr_count_index);
    printf("[before]The original array elements are :\n");
    print_all_arr(arr);
    arr[0] = 1;
    arr[1] = 2;
    arr[2] = 3;
    arr[3] = 4;
    arr[4] = 5;

    /* Point is this one. 
       If I assign arr[5] 30, then, max_arr_count_index is changed also as            
       30. if I assign arr[5] 10000 max_arr_count_index is assigned 10000.
    */

    arr[5] = 30;

    /* Point is this one. 
       If I assign arr[5] 30, then, max_arr_count_index is changed also as            
       30. if I assign arr[5] 10000 max_arr_count_index is assigned 10000.
    */

    printf("[after]max_arr_count_index : %d\n", max_arr_count_index);
    printf("[after]The array elements after arr[5] is assigned 30 :\n");

    print_all_arr(arr);

    return 0;
}

结果如下:

[before]max_arr_count_index : 5
[before]The original array elements are :
arr[0] = 11 
arr[1] = 33 
arr[2] = 55 
arr[3] = 77 
arr[4] = 88 
[after]max_arr_count_index : 30
[after]The array elements after arr[5] is assigned 30 :
arr[0] = 1 
arr[1] = 2 
arr[2] = 3 
arr[3] = 4 
arr[4] = 5 
arr[5] = 30 
arr[6] = 0 
arr[7] = 0 
arr[8] = 0 
arr[9] = 0 
arr[10] = 0 
arr[11] = 0 
arr[12] = 0 
arr[13] = 0 
arr[14] = 0 
arr[15] = 0 
arr[16] = 0 
arr[17] = 0 
arr[18] = 0 
arr[19] = 0 
arr[20] = 0 
arr[21] = 0 
arr[22] = 0 
arr[23] = 0 
arr[24] = 0 
arr[25] = 0 
arr[26] = 0 
arr[27] = 0 
arr[28] = 0 
arr[29] = 0 
Program ended with exit code: 0

越界访问数组会引发未定义的行为。在这种情况下,没有什么好期待的。 arr 的大小是 5。您可以从 arr[0]arr[4] 访问 arr

暂时把 UB 放在一边,对行为的解释

/* Point is this one. 
   If I assign arr[5] 30, then, max_arr_count_index is changed also as            
   30. if I assign arr[5] 10000 max_arr_count_index is assigned 10000.
*/

可能是变量 max_arr_count_index 是在数组 arr 之后声明的。编译器可能会在数组 arr 的最后一个元素之后为 max_arr_count_index 分配内存。例如,如果 arr[4] 位于 0x100,则 max_arr_count_index 的内存分配在 0x104。所以数组 arr 之后是地址 0x104。由于 &arr[5]max_arr_count_index 的地址相同,因此将值赋给 arr[5] 并将该值写入 max_arr_count_index 的地址。请注意,这并不是真正发生的事情。这是对这种行为的直觉。一旦有 UB,那么所有赌注都会取消。

回答关于为什么 未定义的行为 恰好以某种方式起作用的问题是矛盾的,并且是反对票的秘诀。但是因为我已经达到了今天的声望上限,所以我对此更加放松,这是我的消息:

arr[i] 计算为 *(arr + i)。很明显 arrmax_arr_count_index 之间可能没有填充,所以 arr + 5 可能等于 &max_arr_count_index。但是指针运算只在数组中有效。您可以将指针设置为指向数组末尾的指针,但是 取消引用 上的行为是 undefined.

当然,改天,编译器可能会吃掉你的猫。

您在 arr 之后声明了 max_arr_count_index,因此 max_arr_count_index 可能在相对于 arr 的下一个地址中,因此对 arr+5 的赋值是max_arr_count_index 地址的分配。

很明显,就 C 标准而言,这是未定义的行为,编译器可以 make fly demons out of your nose 并且还不错。

但是你想要更深入,因为你要求 "under the hood",所以我们基本上必须寻找汇编器输出。摘录(用 gcc -g test test.cobjdump -S --disassemble test 制作)是:

int main(int argc, const char * argv[]) {
 743:   55                      push   %rbp
 744:   48 89 e5                mov    %rsp,%rbp
 747:   48 83 ec 10             sub    [=10=]x10,%rsp
 74b:   89 7d fc                mov    %edi,-0x4(%rbp)
 74e:   48 89 75 f0             mov    %rsi,-0x10(%rbp)
    // insert code here...
    printf("[before]max_arr_count_index : %d\n", max_arr_count_index);
 752:   8b 05 fc 08 20 00       mov    0x2008fc(%rip),%eax        # 201054 <max_arr_count_index>
 758:   89 c6                   mov    %eax,%esi
 75a:   48 8d 3d 37 01 00 00    lea    0x137(%rip),%rdi        # 898 <_IO_stdin_used+0x18>
 761:   b8 00 00 00 00          mov    [=10=]x0,%eax
 766:   e8 35 fe ff ff          callq  5a0 <printf@plt>
    printf("[before]The original array elements are :\n");
 76b:   48 8d 3d 4e 01 00 00    lea    0x14e(%rip),%rdi        # 8c0 <_IO_stdin_used+0x40>
 772:   e8 19 fe ff ff          callq  590 <puts@plt>
    print_all_arr(arr);
 777:   48 8d 3d c2 08 20 00    lea    0x2008c2(%rip),%rdi        # 201040 <arr>
 77e:   e8 6d ff ff ff          callq  6f0 <print_all_arr>
    arr[0] = 1;
 783:   c7 05 b3 08 20 00 01    movl   [=10=]x1,0x2008b3(%rip)        # 201040 <arr>
 78a:   00 00 00 
    arr[1] = 2;
 78d:   c7 05 ad 08 20 00 02    movl   [=10=]x2,0x2008ad(%rip)        # 201044 <arr+0x4>
 794:   00 00 00 
    arr[2] = 3;
 797:   c7 05 a7 08 20 00 03    movl   [=10=]x3,0x2008a7(%rip)        # 201048 <arr+0x8>
 79e:   00 00 00 
    arr[3] = 4;
 7a1:   c7 05 a1 08 20 00 04    movl   [=10=]x4,0x2008a1(%rip)        # 20104c <arr+0xc>
 7a8:   00 00 00 
    arr[4] = 5;
 7ab:   c7 05 9b 08 20 00 05    movl   [=10=]x5,0x20089b(%rip)        # 201050 <arr+0x10>
 7b2:   00 00 00 
    /* Point is this one. 
       If I assign arr[5] 30, then, max_arr_count_index is changed also as            
       30. if I assign arr[5] 10000 max_arr_count_index is assigned 10000.
    */

    arr[5] = 30;
 7b5:   c7 05 95 08 20 00 1e    movl   [=10=]x1e,0x200895(%rip)        # 201054 <max_arr_count_index>
 7bc:   00 00 00 
    /* Point is this one. 
       If I assign arr[5] 30, then, max_arr_count_index is changed also as            
       30. if I assign arr[5] 10000 max_arr_count_index is assigned 10000.
    */

    printf("[after]max_arr_count_index : %d\n", max_arr_count_index);
 7bf:   8b 05 8f 08 20 00       mov    0x20088f(%rip),%eax        # 201054 <max_arr_count_index>
 7c5:   89 c6                   mov    %eax,%esi
 7c7:   48 8d 3d 22 01 00 00    lea    0x122(%rip),%rdi        # 8f0 <_IO_stdin_used+0x70>
 7ce:   b8 00 00 00 00          mov    [=10=]x0,%eax
 7d3:   e8 c8 fd ff ff          callq  5a0 <printf@plt>
    printf("[after]The array elements after insertion :\n");
 7d8:   48 8d 3d 39 01 00 00    lea    0x139(%rip),%rdi        # 918 <_IO_stdin_used+0x98>
 7df:   e8 ac fd ff ff          callq  590 <puts@plt>

    print_all_arr(arr);
 7e4:   48 8d 3d 55 08 20 00    lea    0x200855(%rip),%rdi        # 201040 <arr>
 7eb:   e8 00 ff ff ff          callq  6f0 <print_all_arr>

    return 0;
 7f0:   b8 00 00 00 00          mov    [=10=]x0,%eax
}

如您所见,即使在那个级别,反汇编程序也已经知道您正在有效地设置 max_arr_count_index。但为什么?

这是因为GCC产生的内存布局就是这样(我们使用-ggcc让它嵌入调试信息,这样反汇编程序就可以知道哪个内存位置是哪个场地)。您有一个由五个 int 组成的全局数组和一个全局 int 变量,它们紧接着彼此声明。全局 int 变量就在内存中的数组后面。因此,访问数组末尾后面的整数会得到 max_arr_count_index.

请记住,访问数组 arr 的元素 i 例如ints 是(至少在所有体系结构上 知道)只是访问内存位置 arr+sizeof(int)*i,其中 arr 是第一个元素的地址.

如前所述,这是未定义的行为。 GCC 还可以在数组之前对全局 int 变量进行排序,这将导致不同的效果,如果该位置没有有效的内存页,甚至可能在尝试访问 arr[5] 时终止程序。