You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 19:01:00