我想知道正在发生什么样的呼唤&和*在C.
是否需要花费大量资源?每当我想获得相同给定变量的地址或将其保存在内存中时,我应该调用&
,即在缓存变量中。同样的*,即当我想得到一个指针值?
示例
void bar(char *str)
{
check_one(*str)
check_two(*str)
//... Could be replaced by
char c = *str;
check_one(c);
check_two(c);
}
答案 0 :(得分:4)
我想知道正在发生什么样的呼唤&和*在C。
没有"呼叫" &
或*
。它们是地址运算符或解除引用运算符,并指示编译器分别使用对象的地址或指针指向的对象。
C不是C ++,所以没有引用;我认为你在你的问题标题中误用了这个词。
在大多数情况下,这基本上是两种看待同一事物的方式。
通常,当您真正想要对象的地址时,您将使用&
。由于编译器需要用它们的地址处理内存中的对象,所以没有开销。
对于使用运算符的具体含义,您必须查看编译器生成的汇编程序。
示例:考虑这个简单的代码,通过godbolt.org反汇编:
#include <stdio.h>
#include <stdlib.h>
void check_one(char c)
{
if(c == 'x')
exit(0);
}
void check_two(char c)
{
if(c == 'X')
exit(1);
}
void foo(char *str)
{
check_one(*str);
check_two(*str);
}
void bar(char *str)
{
char c = *str;
check_one(c);
check_two(c);
}
int main()
{
char msg[] = "something";
foo(msg);
bar(msg);
}
根据供应商和优化设置,编译器输出可以远远狂野地。
clang 3.8使用-O2
check_one(char): # @check_one(char)
movzx eax, dil
cmp eax, 120
je .LBB0_2
ret
.LBB0_2:
push rax
xor edi, edi
call exit
check_two(char): # @check_two(char)
movzx eax, dil
cmp eax, 88
je .LBB1_2
ret
.LBB1_2:
push rax
mov edi, 1
call exit
foo(char*): # @foo(char*)
push rax
movzx eax, byte ptr [rdi]
cmp eax, 88
je .LBB2_3
movzx eax, al
cmp eax, 120
je .LBB2_2
pop rax
ret
.LBB2_3:
mov edi, 1
call exit
.LBB2_2:
xor edi, edi
call exit
bar(char*): # @bar(char*)
push rax
movzx eax, byte ptr [rdi]
cmp eax, 88
je .LBB3_3
movzx eax, al
cmp eax, 120
je .LBB3_2
pop rax
ret
.LBB3_3:
mov edi, 1
call exit
.LBB3_2:
xor edi, edi
call exit
main: # @main
xor eax, eax
ret
请注意,foo
和bar
相同。其他编译器是否做了类似的事情?嗯......
gcc x64 5.4使用-O2
check_one(char):
cmp dil, 120
je .L6
rep ret
.L6:
push rax
xor edi, edi
call exit
check_two(char):
cmp dil, 88
je .L11
rep ret
.L11:
push rax
mov edi, 1
call exit
bar(char*):
sub rsp, 8
movzx eax, BYTE PTR [rdi]
cmp al, 120
je .L16
cmp al, 88
je .L17
add rsp, 8
ret
.L16:
xor edi, edi
call exit
.L17:
mov edi, 1
call exit
foo(char*):
jmp bar(char*)
main:
sub rsp, 24
movabs rax, 7956005065853857651
mov QWORD PTR [rsp], rax
mov rdi, rsp
mov eax, 103
mov WORD PTR [rsp+8], ax
call bar(char*)
mov rdi, rsp
call bar(char*)
xor eax, eax
add rsp, 24
ret
好吧,如果有任何疑问foo
和bar
是等价的,至少是编译器,我认为:
foo(char*):
jmp bar(char*)
是一个强有力的论据,他们确实 。
答案 1 :(得分:2)
在C中,没有与一元&
或*
运算符关联的运行时成本;两者都在编译时进行评估。所以
check_one(*str)
check_two(*str)
和
char c = *str;
check_one( c );
check_two( c );
忽略作业的开销。
在C ++中不一定如此,因为您可以重载这些运算符。
答案 2 :(得分:0)
<强> tldr; 强>
如果用C编程,那么&
运算符用于获取变量的地址,*
用于获取该变量的值,给定它的地址。
这也是为什么在C中,当你将一个字符串传递给一个函数时,你必须说明字符串的长度,否则,如果不熟悉你的逻辑的人看到了函数签名,他们就无法判断函数是否是称为bar(&some_char)
或bar(some_cstr)
。
总而言之,如果您的变量x
类型为someType
,那么&x
将导致someType* addressOfX
,*addressOfX
将导致值x
。 C中的函数仅将指针作为参数,即您无法创建参数类型为&x
或&&x
此外,您的示例可以重写为:
check_one(str[0])
check_two(str[0])
答案 3 :(得分:0)
AFAIK,在x86和x64中,您的变量存储在内存中(如果没有用register
关键字表示)并通过指针访问。
const int foo = 5
等于foo dd 5
且check_one(*foo)
等于push dword [foo]; call check_one
。
如果您创建其他变量c
,则它看起来像:
c resd 1
...
mov eax, [foo]
mov dword [c], eax ; Variable foo just copied to c
push dword [c]
call check_one
除了额外的复制和内存分配外,没有任何改变。 我认为编译器的优化器处理它并使两种情况尽可能快。所以你可以使用更易读的变体。