在 C 语言中使用负数索引有什么缺点吗?

4

在这里已经有人问过,C语言中是否允许使用负索引,但我好奇的是,频繁使用这种技术是否会有任何性能上的劣势。例如,它是否会在某些硬件平台上破坏编译器使用基址+offset索引的能力,或者混淆优化器等。

我之所以问这个问题,是因为我有很多带有以下签名的解码例程:

decode_something(char *buffer, int buffer_size, int bit_position) {
    ...
    if (((bit_position + need_bits) >> 3) < buffer_size) {
       x= buffer[bit_position >> 3] ...
       bit_position += need_bits;
    ...
}

后来我意识到,如果使用指向缓冲区末尾的指针,我可以大大简化所有代码:

decode_something(char *buf_limit, int bits_remaining) {
    ...
    if (need_bits <= bits_remaining) {
        x= buf_limit[ -((bits_remaining+7) >> 3) ];
        bits_remaining -= need_bits;
    ...
}
(好的,实际上这个例子并没有展示它是如何变得“更简单”,但你可以将其推广到所有只需要两个变量调用此函数或保存解析状态的情况。) 现在我正在考虑在整个库中使用这种模式,但我想知道是否有不这样做的原因。

1
对于任何数组,语法foo[bar]只是*(foo + bar)的简写。无论bar是正数还是负数都不应该有任何影响。个人而言,我认为明确地使用指针算术而不是使用索引会使您的代码更清晰,但我想这只是个人偏好。 - Roflcopter4
@Roflcopter4 这可能会阻碍语法糖解码之前的优化。我没有看过任何源代码,但考虑到GCC编写得有多糟糕,这是可能的。使用Clang应该没问题吧,我猜。 - Shambhav Gautam
1
为什么在糖被解码之前会有任何优化呢?毫无疑问,任何编译器在执行任何重要操作之前都会构建AST吧?(注意:我不会对那个关于gcc的喷子做出反应) - Roflcopter4
@Roflcopter4 我知道指针数学的等效性。但我也知道 *(foo + (-bar)) 不是相同的AST,可能会错过某种优化。 - M Conrad
1个回答

1
例如,在某些硬件平台上,它是否会破坏编译器使用基本偏移量索引的能力,或者混淆优化器等。
不会。C语言标准明确定义了它:
从C11标准#6.5.6p8:
当将具有整数类型的表达式添加到指针中或从指针中减去时,结果具有指针操作数的类型。如果指针操作数指向数组对象的元素,并且数组足够大,则结果指向原始元素的偏移量为整数表达式的元素,使得所得到和原始数组元素下标之差相等。换句话说,如果表达式P指向数组对象的第i个元素,则表达式(P)+N(等价于N +(P))和(P)-N(其中N的值为n)分别指向数组对象的第i + n个和第i-n个元素,前提是它们存在。....
只是以防万一,如果您不知道:
从C11标准#6.5.2.1p2,下标运算符的定义:
下标运算符[]的定义是E1 [E2]与(*((E1) + (E2)))相同。
你的另一个问题:
但我想知道频繁使用这种技术是否有任何性能缺陷。
归结为一个问题 - 有两个数字a和b,其中两个操作 - a + b和a-b,哪个操作比另一个更快。
它不是语言决定像加法,减法等操作的时间,而是底层处理器。如果您真的对此感兴趣,必须深入挖掘底层处理器的指令集,并比较相应指令的延迟等等。

我有一个问题,只有对性能问题有积极了解的人才能回答,这不太公平,所以只要没有发生这种情况,我就接受这个答案。 - M Conrad
如果你真的那么担心,可以检查每个版本编译器生成的汇编代码。我敢打赌它们会完全相同。 - Roflcopter4
我并不认为这在大多数情况下有什么影响,但是汇编输出必须稍有不同。性能差异“本身”(在这种情况下)是可以忽略不计的,但是你访问内存的顺序可能会极大地影响CPU缓存以及其访问预测的工作方式。但这取决于具体的CPU架构,因此在这里讨论是没有意义的。 - Adriano Repetti

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接