今天早上我在思考,将一些正数变成负数,将一些负数变成正数的最快方法是什么,当然,最简单的方法可能是:
int a = 10;
a = a*(-1);
或者
int a = 10;
a = -a;
但是,我想,我可以使用shift和指针命令来做到这一点......是否真的有可能使用shift运算符和内存来改变值的符号?
今天早上我在思考,将一些正数变成负数,将一些负数变成正数的最快方法是什么,当然,最简单的方法可能是:
int a = 10;
a = a*(-1);
或者
int a = 10;
a = -a;
但是,我想,我可以使用shift和指针命令来做到这一点......是否真的有可能使用shift运算符和内存来改变值的符号?
a *= -1;
或者
a = -a;
把剩下的交给优化器。
在优化被禁用的情况下,x86架构的gcc将第一个编译为以下汇编代码:
.file "optimum.c"
.def ___main; .scl 2; .type 32; .endef
.text
.globl _main
.def _main; .scl 2; .type 32; .endef
_main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $16, %esp
call ___main # MinGW library init function
movl $10, 12(%esp) ;i = 10
negl 12(%esp) ;i = -i
movl $0, %eax
leave
ret
.file "optimum.c"
.def ___main; .scl 2; .type 32; .endef
.text
.globl _main
.def _main; .scl 2; .type 32; .endef
_main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $16, %esp
call ___main
movl $10, 12(%esp) ;i = 10
negl 12(%esp) ;i = -i
movl $0, %eax
leave
ret
相同的输出!生成的汇编代码没有差别。
--------------------------编辑,问题发布者回答他使用的是VC++2012,英特尔架构-------------------
使用cl optimum.c /Fa optimum.asm
进行编译(禁用优化)。
; Listing generated by Microsoft (R) Optimizing Compiler Version 16.00.30319.01
TITLE C:\Users\Dell\Downloads\TTH\TTH\TTH\optimum.c
.686P
.XMM
include listing.inc
.model flat
INCLUDELIB LIBCMT
INCLUDELIB OLDNAMES
PUBLIC _main
; Function compile flags: /Odtp
_TEXT SEGMENT
_a$ = -4 ; size = 4
_argc$ = 8 ; size = 4
_argv$ = 12 ; size = 4
_main PROC
; File c:\users\dell\downloads\tth\tth\tth\optimum.c
; Line 4
push ebp
mov ebp, esp
push ecx
; Line 5
mov DWORD PTR _a$[ebp], 10 ; 0000000aH
; Line 6
mov eax, DWORD PTR _a$[ebp]
neg eax ;1 machine cycle!
mov DWORD PTR _a$[ebp], eax
; Line 7
xor eax, eax
; Line 8
mov esp, ebp
pop ebp
ret 0
_main ENDP
_TEXT ENDS
END
使用第二种方法(a = a * -1
),禁用了优化 MSVC:
; Listing generated by Microsoft (R) Optimizing Compiler Version 16.00.30319.01
TITLE C:\Users\Dell\Downloads\TTH\TTH\TTH\optimum.c
.686P
.XMM
include listing.inc
.model flat
INCLUDELIB LIBCMT
INCLUDELIB OLDNAMES
PUBLIC _main
; Function compile flags: /Odtp
_TEXT SEGMENT
_a$ = -4 ; size = 4
_argc$ = 8 ; size = 4
_argv$ = 12 ; size = 4
_main PROC
; File c:\users\dell\downloads\tth\tth\tth\optimum.c
; Line 4
push ebp
mov ebp, esp
push ecx
; Line 5
mov DWORD PTR _a$[ebp], 10 ; 0000000aH
; Line 6
mov eax, DWORD PTR _a$[ebp]
imul eax, -1 ;1 instruction, 3 machine/cycles :|
mov DWORD PTR _a$[ebp], eax
; Line 7
xor eax, eax
; Line 8
mov esp, ebp
pop ebp
ret 0
_main ENDP
_TEXT ENDS
END
所以,如果你关心在MSVC下调试模式下汇编代码的性能,你可以相应地优化你的源代码。通常情况下,你只需要关心优化后的构建版本的性能。
gcc
不进行优化! gcc -c optimum.c -S -o optimum.s
- Aniket Ingemovl $10, 12(%esp) // i = 10
和 negl 12(%esp) // i = -i
。@GrijeshChauhan 建议你通过评论向发布者提出这样的更改,而不是自己编辑帖子,因为你会改变内容的含义。 - Lundina = -a
, a *= -1
等等代码生成等效的最优代码(可能是单个指令)。1
然而,*= -1
成语有一个实际优势:您只需要写一次左侧,它只被评估一次-并且读者只需要阅读一次!当LHS很长、复杂或昂贵,或者可能具有副作用时,这是相关的:
(valid ? a : b)[prime_after(i++)] *= -1;
*look_up (input) *= -1; // Where look_up may have side-effects
parity[state][(unsigned int)getc(stdin)] *= -1;
variable_with_a_long_explanatory_name *= -1;
一旦人们采用了某种习语,他们往往在其他情况下也会坚持使用它。
1 Peter Cordes的观察:几乎所有编译器都理解a = -a
和a *= -1
是完全相同的,并将发出无论如何在目标CPU上最有效的汇编代码,无论您如何编写它。(例如Godbolt编译器探索器用于x86 gcc/MSVC/clang和ARM gcc。)
但是,尽管MSVS 2012(仅在调试模式下)对每个操作使用一个指令,但它们在最近的英特尔CPU上需要1个周期进行= -a
和3个周期进行*= -1
,使用实际的imul
指令。
还有 0 - n
Gcc 对于所有四种情况都会发出"neg"指令:-n,0 - n,n * -1和~n + 1。
sizeof(int) == sizeof(Cpu_register)
,那么“将这个数字变为负数”将是一条指令(通常称为neg
)[可能需要加载和存储值,但如果您在使用变量进行其他操作,则可以在加载后保留该变量,并稍后仅存储该值...]。-1
可能比a = -a;
慢,但大多数合格的编译器应该能够使这两者等效。使用高级语言的解决方案
像这样的问题在面试和竞技编程领域很受欢迎。
我来到这里是为了寻找更多不使用 - 或 + 运算符进行数字否定的解决方案。
具体步骤如下:
> int addNumbers(int x, int y)
> {
> if(y==0) return x; // carry is 0
> return addNumbers(x^y,(x&y)<<1);
> }
在这里,x^y执行比特加法,而x&y则处理进位操作。你可以尝试一下
int a = 10;
a= ~a+1;
但你不必担心这个问题,因为编译器会以最佳方式处理它。
neg
(在像x86和ARM这样的2的补码机器上,这些操作在一条指令中提供)。如果您使用unsigned
(并且仅在位操作后转换回int
),它具有不在INT_MIN上产生UB的有趣优势,而-a
或a * -1
将会是有符号溢出。这是唯一的优点。它对可读性很差,而且不可移植。 - Peter Cordes-a
对无符号值不确定行为(UB)? - riciint a = INT_MIN
,做 -a
是未定义的行为,但是 (int)(~(unsigned)a + 1)
不是。当然,现在我想想,你可以直接做 (int)-(unsigned)a
得到相同的结果(C++ 实现中的二进制补码/无符号取反,甚至适用于一的补码或正负数表示法)。所以算了,这没有任何优势。(关于效率:我只是说这不会更糟。绝对不是比 -
和 *= -1
更好!正如你所说,它与 -
和 *= -1
的编译结果一样。) - Peter Cordes
a = -a;
更快。事实上,我不明白为什么您认为编译器在编译a = -a;
时不一定会使用最有效的实现。 - Mr Lister