一个简单的 C 程序,但是不明白区别在哪里
#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 呢😅
速查卡不仅仅可能帮助我们记住一些重要的东西,而且可以放在手边,当我们需要的时候,可以很快地查找。 在本篇文章中,你可以看到28个相当不错的关于Web设计的速查卡,它们分别是关于…
简介: 这是一个活动主题,参与这个活动的方式可以是晒出 "Google 眼中的你" 的截图,也可以是友好的评论其他的用户分享的 "Google 眼中的他"。 我们不希望您对他人…
我稍微抱怨了一下,说对比其他家,价格太贵,结果人家很坦诚,说这两年是有点。 人家继续补充:但是放心,从今年开始,大家的价格都一样了,不用担心了。 我听了以后心里一万屁草泥马啊,…