在C语言中,为什么我可以看到一个写在数组末尾的值出现在另一个变量中?

b0zn9rqh  于 2022-12-29  发布在  其他
关注(0)|答案(4)|浏览(140)

这些天来,我把业余时间都花在了做一些有趣的事情上,让自己在C语言中感到快乐......
但是,我最终发现了一些有趣的事情,我不知道为什么这个结果直到现在才发生。
根据arr[5]值分配max_arr_count_index,该值超出数组末尾+1。
有人能给我解释一下吗?我知道不应该这样,我给数组的过去一个索引赋值(这里,arr[5] = 30),这是不安全的,而且是标准定义的undefined behavior
我不会在真实的中做同样的事,但是,我只想了解更多.
Clang和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
nwwlzxa7

nwwlzxa71#

很明显,就C标准而言,这是未定义的行为,编译器可以make fly demons out of your nose,它会很好。
但是如果您想深入了解,就像您要求的“引擎盖下”一样,那么我们实际上必须查找汇编器的输出。摘录(使用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    $0x10,%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    $0x0,%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   $0x1,0x2008b3(%rip)        # 201040 <arr>
 78a:   00 00 00 
    arr[1] = 2;
 78d:   c7 05 ad 08 20 00 02    movl   $0x2,0x2008ad(%rip)        # 201044 <arr+0x4>
 794:   00 00 00 
    arr[2] = 3;
 797:   c7 05 a7 08 20 00 03    movl   $0x3,0x2008a7(%rip)        # 201048 <arr+0x8>
 79e:   00 00 00 
    arr[3] = 4;
 7a1:   c7 05 a1 08 20 00 04    movl   $0x4,0x2008a1(%rip)        # 20104c <arr+0xc>
 7a8:   00 00 00 
    arr[4] = 5;
 7ab:   c7 05 9b 08 20 00 05    movl   $0x5,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   $0x1e,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    $0x0,%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    $0x0,%eax
}

正如您所看到的,即使在这个级别,反汇编器也已经知道您正在有效地设置max_arr_count_index
这是因为GCC产生的内存布局就是这样(并且我们将-ggcc一起使用,使其嵌入调试信息,以便反汇编器可以知道哪个内存位置是哪个字段)。您有一个包含五个int的全局数组,以及一个全局int变量,全局int变量就在内存中数组的后面,访问数组末尾后面的整数得到max_arr_count_index
请记住,对例如int s的数组arr的元素i的访问(至少在所有架构 I 已知上)是简单地访问存储器位置arr+sizeof(int)*i,其中arr是第一个元素的地址。
如前所述,这是一个未定义的行为。GCC也可以将全局int变量置于数组之前,这将导致不同的效果,甚至可能在试图访问arr[5]时,如果该位置没有有效的内存页面,程序将终止。

7vux5j2d

7vux5j2d2#

访问超出界限的数组会调用未定义的行为。在这种情况下不会有什么好结果。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之后。编译器可能为max_arr_count_index分配的内存刚好超过数组arr的最后一个元素。例如,如果arr[4]0x100,那么max_arr_count_index的内存分配在0x104,所以数组arr后面是地址0x104。由于&arr[5]max_arr_count_index是同一个地址,所以给arr[5]赋值,并将该值写入max_arr_count_index的地址。请注意,这并不是实际发生的事情。这是对这种行为的直觉。一旦出现UB,所有的赌注都将结束。

rur96b6h

rur96b6h3#

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

fjaof16o

fjaof16o4#

回答为什么“未定义的行为”会以某种方式起作用的问题是矛盾的,也是投反对票的秘诀。但鉴于我已经达到了今天的声誉上限,因此我对这一点要放松得多,以下是我的信:
arr[i]的计算结果为*(arr + i)。很明显,arrmax_arr_count_index之间可能没有填充,因此arr + 5可能等于&max_arr_count_index。但指针算法仅在数组中有效。您可以将指针设置为超过数组末尾的指针,但 * 取消引用 * 时的行为是 undefined
当然,再过一天,编译器可能会吃掉你的猫。

相关问题