我正在与用于DSP芯片的编译器一起工作,该编译器故意从C代码中生成可访问数组末尾的代码,而C代码则不会!
这是因为循环是经过结构化的,因此迭代结束时会为下一次迭代预取一些数据。因此,在最后一次迭代结束时预取的数据实际上从未使用过。
像这样编写C代码会调用未定义的行为,但这只是标准文档中的一种形式,该文档本身具有最大的可移植性。
并非经常如此,无法巧妙地优化访问范围的程序。这简直是越野车。该代码获取一些垃圾值,并且与上述编译器的优化循环不同,该代码随后在后续计算中使用该值,从而破坏了主题。
值得捕获这样的错误,因此仅出于这个原因就值得使行为未定义:这样,运行时就可以生成诊断消息,例如“ main.c第42行中的数组溢出”。
在具有虚拟内存的系统上,可能恰好分配了一个数组,使得其后的地址位于虚拟内存的未映射区域中。然后访问将轰炸该程序。
顺便说一句,请注意,在C语言中,我们允许创建一个比数组末尾更远的指针。而且此指针必须比任何指向数组内部的指针都更大。这意味着C实现无法将数组直接放在内存的末尾,在内存末尾,一个加号地址将环绕并且看起来比数组中的其他地址小。
但是,即使未最大程度地移植,访问未初始化或超出范围的值有时仍是一种有效的优化技术。例如,这就是为什么Valgrind工具不会在未初始化的数据发生访问时报告该访问,而是仅在以后以某种可能会影响程序结果的方式使用该值时才报告对未初始化数据的访问的原因。您会得到类似“ xxx:nnn中的条件分支取决于未初始化的值”的诊断信息,有时可能很难找到它的起源。如果所有此类访问都被立即捕获,则编译器优化的代码以及正确的手工优化的代码将产生许多误报。
说到这些,我正在与一家供应商合作,在移植到Linux并在Valgrind下运行时会释放这些错误。但是供应商说服我,只有几个位实际使用的值中的大部分来自未初始化的内存,逻辑上小心地避免了这些位。.仅使用了该值的好位,而Valgrind没有能力追踪到单个位。未初始化的材料来自读取已编码数据的位流末尾的单词,但是代码知道该流中有多少位,并且不会使用比实际更多的位。由于超出位流阵列末尾的访问不会对DSP架构造成任何损害(阵列之后没有虚拟内存,没有内存映射端口,并且地址没有包装),因此这是一种有效的优化技术。
“未定义行为”的含义并不多,因为根据ISO C,仅包括未在C标准中定义的标头或调用程序本身或C标准中未定义的函数就是未定义的示例。行为。未定义的行为并不意味着“地球上没有任何人定义”,而仅意味着“ ISO C标准未定义”。但当然,有时未定义的行为真的是绝对不会被任何人所定义。