C指针和引用
我想知道在C中调用和*到底发生了什么。C指针和引用,c,pointers,reference,C,Pointers,Reference,我想知道在C中调用和*到底发生了什么。 是不是要花费很多资源?每次我想获取同一给定变量的地址时,我应该调用&,还是将其保存在内存中,即缓存变量中。*也一样,比如当我想得到一个指针值时 示例 void bar(char *str) { check_one(*str) check_two(*str) //... Could be replaced by char c = *str; check_one(c); check_tw
是不是要花费很多资源?每次我想获取同一给定变量的地址时,我应该调用
&
,还是将其保存在内存中,即缓存变量中。*也一样,比如当我想得到一个指针值时
示例
void bar(char *str)
{
check_one(*str)
check_two(*str)
//... Could be replaced by
char c = *str;
check_one(c);
check_two(c);
}
我想知道在C中调用和*到底发生了什么
没有所谓的“调用”&
或*
。它们是地址运算符或解引用运算符,分别指示编译器处理对象的地址或指针指向的对象
和C不是C++,所以没有引用;我想你只是在问题的标题中误用了那个词
在大多数情况下,这基本上是两种看待同一事物的方式 通常,当您实际需要对象的地址时,将使用&
。由于编译器无论如何都需要处理内存中的对象及其地址,因此没有开销
对于使用运算符的具体含义,您必须查看编译器生成的汇编器
例子:考虑这个琐碎的代码,通过:
分解 请注意,foo
和bar
是相同的。其他编译器也做类似的事情吗?嗯
gcc x64 5.4使用-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
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*)
这是一个强有力的论据,事实确实如此
我想知道在C中调用和*到底发生了什么
没有所谓的“调用”&
或*
。它们是地址运算符或解引用运算符,分别指示编译器处理对象的地址或指针指向的对象
和C不是C++,所以没有引用;我想你只是在问题的标题中误用了那个词
在大多数情况下,这基本上是两种看待同一事物的方式 通常,当您实际需要对象的地址时,将使用&
。由于编译器无论如何都需要处理内存中的对象及其地址,因此没有开销
对于使用运算符的具体含义,您必须查看编译器生成的汇编器
例子:考虑这个琐碎的代码,通过:
分解 请注意,foo
和bar
是相同的。其他编译器也做类似的事情吗?嗯
gcc x64 5.4使用-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
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*)
这是一个强有力的论据,事实确实如此 在C中,一元
&
或*
运算符都没有运行时成本;两者都在编译时进行计算。所以在运行时,两者之间没有区别
check_one(*str)
check_two(*str)
及
忽略分配的开销
<>这在C++中不一定是真的,因为你可以重载那些运算符。 在C中,一元
&
或*
运算符都没有运行时成本;两者都在编译时进行计算。所以在运行时,两者之间没有区别
check_one(*str)
check_two(*str)
及
忽略分配的开销
<>这在C++中不一定是真的,因为你可以重载那些运算符。 tldr强>
如果您是用C语言编程,那么&
运算符用于获取变量的地址,*
用于获取给定地址的变量的值
这也是为什么在C中,当您将字符串传递给函数时,必须声明字符串的长度的原因,否则,如果不熟悉您的逻辑的人看到函数签名,他们无法判断函数是否被称为
bar(&some_char)
或bar(some_cstr)
总之,如果您有一个someType
类型的变量x
,那么&x
将产生someType*addressOfX
,*addressOfX
将产生x
的值。C中的函数仅将指针作为参数,即不能创建参数类型为&x
或&x
的函数
您的示例也可以改写为:
check_one(str[0])
check_two(str[0])
tldr强>
如果您是用C语言编程,那么&
运算符用于获取变量的地址,*
用于获取给定地址的变量的值
这也是为什么在C中,当您将字符串传递给函数时,必须声明字符串的长度的原因,否则,如果不熟悉您的逻辑的人看到函数签名,他们无法判断函数是否被称为
bar(&some_char)
或bar(some_cstr)
总之,如果您有一个someType
类型的变量x
,那么&x
将产生someType*addressOfX
,*addressOfX
将产生x
的值。C中的函数仅将指针作为参数,即不能创建参数类型为&x
或&x
的函数
您的示例也可以改写为:
check_one(str[0])
check_two(str[0])
好的,在x86和x64中,变量存储在内存中(如果未使用
register
关键字声明),并由指针访问。
const int foo=5
等于foo dd 5
和检查一个(*foo)
等于push dword[foo];请致电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
除了额外的复制和内存分配之外,没有任何变化。
我认为编译器的优化器处理它,并使这两种情况的速度与我想象的一样快