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

我想知道在C中调用和*到底发生了什么。
是不是要花费很多资源?每次我想获取同一给定变量的地址时,我应该调用
&
,还是将其保存在内存中,即缓存变量中。*也一样,比如当我想得到一个指针值时

示例

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
除了额外的复制和内存分配之外,没有任何变化。 我认为编译器的优化器处理它,并使这两种情况的速度与我想象的一样快