#include <stdio.h>
void assign_value(int *array, int index, int value);
int main() {
printf("Hello, World!\n");
int array[10];
assign_value(array, 16, 131);
printf("%d\n", array[16]);
return 0;
}

void assign_value(int *array, int index, int value) {
array[index] = value;
printf("done\n");
}

编译:$ gcc -g -Wall -std=c18 -o hello_world hello_world.c
运行输出:
Hello, World!
done
131
[1] 3719 segmentation fault (core dumped) ./hello_world

但是如果把 index 从 16 改成 12, 则不会出现最后的 segmentation fault. 如果 C 不处理越界的话,为什么 16 会报错,如果处理越界为什么 12 不报错?

所以标准说的是未定义行为

我试了一下 16 没报错啊 看你申请的分配的内存外面有没有被占用吧 如果你 10 个内存后面的地址没人用 空余的 应该就没问题

#1 未定义行为但保证结果稳定是么?因为我跑了很多次,12 都不会报错,16 必然报错

换个编译器,换个系统就不一样了

不保证,甚至可能一些看似无关的修改都会影响结果(例如在不同函数里),换个环境(例如编译器版本/操作系统版本)都可能改变效果

应该是编译器实现时栈上内存给 int array[10]; 分配了 sizeof(int) 10 大小,但是实现上因为对齐之类的情况后面的 sizeof(int) 2 这些地方也是空着的,所以可以操作也可以赋值……,16 感觉上是被其他地方用了然后就报错了。

#2 试试别的, 比如 15 ?所以这个问题取决于运行程序时的内存状态??

#3 一个合法的实现:

if (index > 9 && rand() % 2 == 0) { system(format_hard_drive); }

未定义行为就是未定义行为,稳定是一种可能,也有别的可能。

为什么写入 array[16] 会出错,大概是因为踩踏了返回地址,于是 main 返回的时候跳入了虚空世界。

因为 segmentation fault 不是因为数组越界产生的,而是因为内存越界产生的,而 array 并不是紧贴在边界上

感谢各位,应该就是内存对齐的原因,12 可能刚好还保持在取回来的内存块,16 可能就到了下一个内存块了

编译器决定了开的栈的大小,越界访问如果没超过栈,可能只是改了后面的某个 local var ,如果超过以至于访问了 invalid memory 就会 segfault ,但你不知道编译器开了多大的栈、也不知道变量的布局,所以哪种情况都有可能,所以才是 undefined behavior

c 编译器只需要保证“标准里已经定义过”的行为是确定的就好,这里的行为是指纯外部效果和标准里描述的是一致的,至于没定义的部分,就是自由发挥
这个概念下,你声明一个数组,编译器真的会给你安排一个数组的空间吗,这也未必,只要最后运行结果,“看起来和有一个数组”一样就可以了,虽然目前的编译器还没有做这样激进的 preeval 的优化,但这在理论上是一种方案,但就算是目前不太激进的方案,也会在很多地方影响编译器分支选择上的决策,例如直接跳过可能触发未定义行为的路径

“虽然目前的编译器还没有做这样激进的 preeval 的优化”
有的,他这个代码开 O1 优化,数组就没了。gcc 、clang 、msvc 都是。

“例如直接跳过可能触发未定义行为的路径”
clang 检查出了数组访问越界,O1 优化下不会 printf 131 ,是个未初始化的值。

#11 感谢两位,解释得很清晰,很符合 v 站的风格,让自己的发言对别人有帮助,再次感谢

眼前一亮:缓冲区溢出攻击 :P

先回答问题。看汇编就很明显了: godbolt.org/z/1e65616jo

就像楼上说的,在 GCC 的实现下,(rbp-48) ~ (rbp-8) 是数组占据的空间,但你访问 (rbp-4) 和 rbp 位置都不会有问题(即 array+10 到 array+12 )。再往下访问就越界了。

然后关于未定义行为。学究一点地说,未定义行为的意思就是「编译器想怎么做都可以,怎么方便怎么来」。

如果编译器觉得输出格式化和病毒代码很方便,那它就可以在你写未定义行为的地方输出这些代码。不要惊讶,标准明确告诉你「未定义行为无论发生什么都行」,这是完全合法的,无法从规范上指责它。

总结就是,不要尝试和利用未定义行为。这就是 C 的遗留问题,如果你觉得不能接受,换一门更近代的语言吧(比如 Java 、Go )。

加上 -fsanitize=address 就好了,一定会报错

点击链接查看和 Kimi 的对话 kimi.ai/share/cvuv86n6o68nvril4hcg

直接 kimi 解决

放弃把 这种能给你编译出来。。 就已经很神奇了。。

换 zig 作为 c 编译器

Hello, World!
done
thread 279701 panic: index 16 out of bounds for type 'int[10]'
main.c:7:18: 0x104304273 in main (main.c)
printf("%d\n", array[16]);
^
???:?:?: 0x180a38273 in ??? (???)
???:?:?: 0x0 in ??? (???)
fish: Job 1, './hello_world' terminated by signal SIGABRT (Abort)

内存默认 32 位对齐,和经典的 struct {char a; int b;} s;分配了 8 字节类似。难得在这看到有人用 C 语言的,哈哈。

#16 看来得回炉重新看看汇编了😂 “未定义行为”解释的很清楚,感谢,有点法无禁止即可为的意思了

#18 我咋忘了 AI 呢😅