在 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)
。很明显 arr
和 max_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.c
和 objdump -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产生的内存布局就是这样(我们使用-g
和gcc
让它嵌入调试信息,这样反汇编程序就可以知道哪个内存位置是哪个场地)。您有一个由五个 int 组成的全局数组和一个全局 int 变量,它们紧接着彼此声明。全局 int 变量就在内存中的数组后面。因此,访问数组末尾后面的整数会得到 max_arr_count_index
.
请记住,访问数组 arr
的元素 i
例如int
s 是(至少在所有体系结构上 我 知道)只是访问内存位置 arr+sizeof(int)*i
,其中 arr
是第一个元素的地址.
如前所述,这是未定义的行为。 GCC 还可以在数组之前对全局 int 变量进行排序,这将导致不同的效果,如果该位置没有有效的内存页,甚至可能在尝试访问 arr[5]
时终止程序。
这些天我用业余时间做一些测试、有趣的事情和实现一些简单的算法、数据结构等各种东西,以我个人对 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)
。很明显 arr
和 max_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.c
和 objdump -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产生的内存布局就是这样(我们使用-g
和gcc
让它嵌入调试信息,这样反汇编程序就可以知道哪个内存位置是哪个场地)。您有一个由五个 int 组成的全局数组和一个全局 int 变量,它们紧接着彼此声明。全局 int 变量就在内存中的数组后面。因此,访问数组末尾后面的整数会得到 max_arr_count_index
.
请记住,访问数组 arr
的元素 i
例如int
s 是(至少在所有体系结构上 我 知道)只是访问内存位置 arr+sizeof(int)*i
,其中 arr
是第一个元素的地址.
如前所述,这是未定义的行为。 GCC 还可以在数组之前对全局 int 变量进行排序,这将导致不同的效果,如果该位置没有有效的内存页,甚至可能在尝试访问 arr[5]
时终止程序。