C指针和引用

时间:2016-11-07 16:48:58

标签: 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_two(c);
}

4 个答案:

答案 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

请注意,foobar 相同。其他编译器是否做了类似的事情?嗯......

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

好吧,如果有任何疑问foobar是等价的,至少是编译器,我认为:

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 5check_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

除了额外的复制和内存分配外,没有任何改变。 我认为编译器的优化器处理它并使两种情况尽可能快。所以你可以使用更易读的变体。