FFmpeg旧版代码中指针加法溢出是否属C99标准未定义行为?
问题分析:这段FFmpeg代码是否依赖C标准的未定义行为?
首先看这段代码的核心逻辑:
#include <stdio.h> #include <stdint.h> #include <stdlib.h> struct foo { int16_t (*ac_val_base)[16]; int16_t (*ac_val[3])[16]; }; int main(int argc, char *argv[]) { struct foo bar; int16_t *ac_val, *ac_val1; bar.ac_val_base = malloc(4639 * 16 * sizeof(int16_t)); bar.ac_val[0] = bar.ac_val_base + 66; ac_val = bar.ac_val[0][0] + 3780 * 16; ac_val1 = ac_val; printf("Result: %d\n", (int) (((char *) ac_val1) - ((char *) bar.ac_val[0][0]))); return 0; }
核心争议点:C99标准6.5.6/8条款的解读
C99标准6.5.6/8条款明确规定:
当整数类型表达式与指针相加或相减时,结果为指针操作数的类型。若指针操作数指向数组对象的元素,且数组足够大,则结果指向与原元素偏移量等于整数表达式的元素。此外,若指针操作数和结果指向同一个数组对象的元素,或指向数组末尾后一位,则不会产生溢出;否则行为未定义。
问题的关键在于:bar.ac_val[0][0]退化为int16_t*指针后,它所关联的「数组对象」到底是什么?
bar.ac_val_base是指向int16_t[16]类型数组的指针,malloc分配的内存被视为包含4639个int16_t[16]元素的大数组对象。bar.ac_val[0] = bar.ac_val_base +66,指向这个大数组的第67个int16_t[16]子数组对象。bar.ac_val[0][0]是这个子数组的第一个元素,退化为int16_t*时,严格来说它关联的数组对象是这个仅包含16个int16_t元素的子数组。
当执行bar.ac_val[0][0] + 3780*16时,偏移量(3780*16=60480个int16_t)远远超出了该子数组的大小(16个元素),结果指针指向的是其他子数组对象的元素。按照C99标准的字面解释,这种跨数组对象的指针运算属于未定义行为。
主流编译器与vbcc的差异原因
主流编译器(GCC、MSVC等)出于实用性考虑,将malloc分配的连续内存视为一个整体的字节块,指针运算直接基于字节偏移计算,忽略了标准中「数组对象边界」的严格限制,因此运行结果符合预期(120960字节)。
而vbcc编译器严格遵循C99标准的字面定义,认为跨数组对象的指针运算属于未定义行为,因此可以进行任意优化(比如指针地址的模运算),最终得到了-8000的结果。
结论
按照C99标准的严格解读,这段代码确实依赖未定义行为。主流编译器的运行结果符合预期,只是它们对标准做了实用性的扩展实现,而非标准强制要求的行为。
内容的提问来源于stack exchange,提问作者Andreas
相关产品推荐
相关产品推荐

