arr[0] = arr[1] = C 中的值是不好的做法吗?

Posted

技术标签:

【中文标题】arr[0] = arr[1] = C 中的值是不好的做法吗?【英文标题】:Is bad practice do arr[0] = arr[1] = value in C? 【发布时间】:2020-08-19 07:19:37 【问题描述】:

正如我上面所说,这是不好的做法吗?在 ASM 中它会是什么样子?我的意思是,我不知道它是否被翻译成这样:

arr[0] = value;
arr[1] = value;

或者是这样的:

arr[1] = value;
arr[0] = arr[1];

第二个显然效率更低(imm vs mem)。

提前致谢。

【问题讨论】:

好吧,我想,任何好的编译器都会优化它。而在 (x86) 汇编器中,它更像是“缓存与依赖链”。 编译器非常擅长优化这类事情。您不必太担心这里的性能,因为如果启用优化,编译器可能会从两者生成相同的代码。请注意,如果您对此感兴趣,大多数 C 编译器都提供了一种检查其汇编输出的方法。 只做前者。可读且高效。后者的意义何在?可读性差,效率也不高。 后者符合语言而前者不符合,尽管优化会定期绕过中间值。如果变量是全局的,那么有一个时间假设,我希望是后者。 【参考方案1】:

如果您想查看 asm,可以使用

objdump -d -M intel binary_name

然后您将搜索.text 部分,然后搜索main 函数。


这是我在计算机上使用int arr[2] 和一个简单的printf 来使用变量的结果。

优化输出,用-O3编译

mov    ecx,0x2a
mov    edx,0x2a

未优化

对于此代码:

arr[0] = 42;
arr[1] = 42;

输出是:

mov    DWORD PTR [rbp-0x10],0x2a
mov    DWORD PTR [rbp-0xc],0x2a

对于这段代码:

arr[0] = arr[1] = 42;

输出是:

mov    DWORD PTR [rbp-0xc],0x2a
mov    eax,DWORD PTR [rbp-0xc]
mov    DWORD PTR [rbp-0x10],eax

在第二种情况下,还有一个额外的操作。

所以经过优化的编译,没有区别,但是为了代码的可读性我不会这样写。

【讨论】:

我很惊讶这两者之间存在差异,尤其是在您开启优化的情况下。 @RussSchultz 未优化时会有所不同,我已经用优化的输出进行了更新,是一样的。 @RussSchultz:您可以从 [rbp - ...] 用于本地人 (-fno-omit-frame-pointer) 以及刚刚存储的值的重新加载中看到,这是未优化(反优化)调试模式代码。 Why does clang produce inefficient asm with -O0 (for this simple floating point sum)? 基本上适用于所有编译器。一些编译器甚至可能在调试模式下的一条语句 a=b=42 语句中进行了优化,但这个编译器没有。优化后的代码完全优化了数组,只将值具体化为 printf 的寄存器参数。 有趣的事实:mov ecx,edx 在这种情况下实际上是一个不错的选择,可以节省一些代码大小(2 字节与 5 字节 mov r32, imm32)。但是编译器不知道printf 很慢并且在一段时间内不会到达它的第 4 个参数(ECX),而且 1 个周期的依赖无论如何都是微不足道的。【参考方案2】:

好吧,我查看了汇编代码,得到了这个:

182             Arr[0] = 0x01;
e357:   A601 LDA #0x01
e359:   C70100 STA 0x0100
184             Arr[0] = Arr[1] = 0x58;
e35d:   A658 LDA #0x58
e35f:   C70101 STA 0x0101
e362:   C70100 STA 0x0100

所以如果我执行 Arr[0] = 0x58;并立即 Arr[1] = 0x58;是:

e357:   A601 LDA #0x58
e359:   C70100 STA 0x0100
e359:   C70100 STA 0x0101

(STA:存储累加器 | LDA:负载累加器)

所以,我使用的编译器优化了代码。你们已经说过,我假设更好的做法(或更好的可读方式)使用:

Arr[0] = 0x58; 
Arr[1] = 0x58;

谢谢大家!

【讨论】:

如果您的常量只是一个数字文字,您可能希望通过编写Arr[0] = Arr[1] = ... 来避免重复它。否则,是的,为该值重复一些有意义的符号名称通常很好。为了良好的风格,您应该真正避免的唯一事情是将Arr[1] = Arr[0]; 作为单独的语句放在单独的行上。 那么你当然应该再次使用value【参考方案3】:

根据 C 语法赋值运算符从右到左求值。

所以这个说法

arr[0] = arr[1] = value;

等价于

arr[0] = ( arr[1] = value );

来自 C 标准(6.5.16 赋值运算符)

3 赋值运算符将值存储在由指定的对象中 左操作数。 赋值表达式具有左边的值 赋值后的操作数,111) 但不是左值。一个类型 赋值表达式是左操作数之后的类型 左值转换。更新存储值的副作用 左操作数在左的值计算之后排序,并且 右操作数。操作数的计算是无序的。

所以根据报价你可以考虑这个说法

arr[0] = arr[1] = value;

喜欢

arr[1] = value;
arr[0] = arr[1];

至于效率,编译器可以生成与您想象的源代码不同的高效目标代码。

【讨论】:

以上是关于arr[0] = arr[1] = C 中的值是不好的做法吗?的主要内容,如果未能解决你的问题,请参考以下文章

`if (idx < arr.length)` 是不是等同于 `if (arr[idx])`?

为啥数组的值是undefined

线段树2 求区间最小值

编程语言中的值数据类型和引用数据类型之间的区别

2022-03-18:arr数组长度为n, magic数组长度为m 比如 arr = { 3, 1, 4, 5, 7 },如果完全不改变arr中的值, 那么收益就是累加和 = 3 + 1 + 4 +

JAVA中,返回值是啥意思?