超出数组最大索引的未定义行为

Posted

技术标签:

【中文标题】超出数组最大索引的未定义行为【英文标题】:Undefined behavior beyond the max index of an array 【发布时间】:2019-11-29 08:17:54 【问题描述】:

情况

我正在参加一个速成课程来熟悉 C,并且我注意到本课程的作者可以打印超出数组索引的数组值,并且确信该值每次都会是 0

以下速成课程的代码:

int arrayVar[] = 45, 67, 34, 23;
printf("This array index value is %d", arrayVar[4]);

代码输出:

This array index value is 0

根据我的经验,在对 C 进行修补/测试期间,一旦超出数组的最大索引,就会进入 未定义行为 领域,其中 任何东西可能发生,那么他怎么能如此自信(并且正确)每次看到0 值?

如果我打印超出数组最大索引的值,我会看到“随机”值(或者,留在内存中的值,对吗?)。

为什么我的体验与我在本课程中看到的不同?这只是C标准的差异吗?或者这是否表明编译器存在差异?还是两者兼有?

环境信息:我使用的是 C11 标准,并且我使用的是(我很确定)ubuntu 默认提供的编译器,位于/usr/bin/cc

编辑:对于有兴趣了解我所看到内容的任何人,这里是课程链接(您可能会被提示登录 Udemy):https://www.udemy.com/c-fast-crash-course-introduction/learn/lecture/12868540#questions

【问题讨论】:

你是对的,这是未定义的行为。 确实存在差异。这似乎不是一门很好的课程。 "... 本课程的作者可以打印超出数组索引的数组值,并确信该值每次都会为 0。" - 这是不正确的。在 C 和 C++ 中保持指向数组后面一个元素的指针是可以的。但是,您不能取消引用该指针,因为那是 UB。允许一个有效指针在数组末尾之后允许循环控制,如while (begin != end) ... begin++; 从a good book学习C,而不是“速成课程”。 @WeatherVane 也许这是免费的原因! ¯\_(ツ)_/¯ 【参考方案1】:

课程作者写错了。

就这么简单。

【讨论】:

基于赞成票和证实的 cmets,我愿意接受这个答案 - 我现在只需要知道,既然是这样,这家伙是如何获得 0在他的视频中输出?他得到0 是偶然的吗?比如,在录制这个过程中,一个0 坐在记忆中的那个地方?是这样吗? 是的,只是机会。这是因为很可能在此之前的内存没有分配。有些编译器可能只是将未使用的内存初始化为 0。 好吧。我真的很感谢大家对此的意见。非常感谢 @Lightness Races in Orbit 以及其他所有人!【参考方案2】:
    未定义并不意味着随机。在许多情况下,未定义通常会导致一些默认行为,因此可能会在很长一段时间内被忽视。内存通常用零初始化,因此访问未初始化的内存通常会产生零。这就是为什么一些内存调试器库会用不常见的值(例如 0xDEADBEEF)填充分配的内存,这些值更有可能触发问题。 内存分配非常重要。底层库需要跟踪分配的内容和空闲的内容,有不同类型的分配(堆栈与堆、数据段、BSS ......)。库可能具有分配某些小对象等的优化策略 - 您不会调用操作系统来分配 16 个字节,但“情况很复杂”。当您分配 16 个字节时,您的 C 库可能会要求几兆字节(如果以前没有这样做的话),内核会假装它把所有这些内存都给了应用程序(假设通常不是所有这些都被使用过)和然后,该库会用您的 16 个字节加上一些内存管理开销来切割一个块。通常与 8 字节边界对齐,因为在字节级别上对内存进行微观管理是一个坏主意,原因有很多。所以下一个整数可能在这个兆字节中已经分配和清除以供将来使用。 (虽然在这种特殊情况下,数组应该在数据部分中并且从未分配过,但想法是相似的 - 接下来可能有一些静态变量恰好为零。您可能想查看二进制数据段的转储布局。)

【讨论】:

以上是关于超出数组最大索引的未定义行为的主要内容,如果未能解决你的问题,请参考以下文章

C++ SSE:存储到数组后的未定义行为

数组中音频的未定义错误

Php和Sql,未定义的未定义索引[重复]

fieldoffset 的未定义行为[重复]

StructLayout 和 FieldOffset 的未定义行为

v-for 和自定义组件的未定义行为