Romain-p Romain-p - 1 month ago 18
C Question

C pointers and references

I would like to know what's really happening calling & and * in C.

Is that it costs a lot of resources? Should I call

&
each time I wanna get an adress of a same given variable or keep it in memory i.e in a cache variable. Same for * i.e when I wanna get a pointer value ?

Example

void bar(char *str)
{
check_one(*str)
check_two(*str)

//... Could be replaced by

char c = *str;

check_one(c);
check_two(c);
}

Answer

I would like to know what's really happening calling & and * in C.

There's no such thing as "calling" & or *. They are the address operator, or the dereference operator, and instruct the compiler to work with the address of an object, or with the object that a pointer points to, respectively.

And C is not C++, so there's no references; I think you just misused that word in your question's title.

In most cases, that's basically two ways to look at the same thing.

Usually, you'll use & when you actually want the address of an object. Since the compiler needs to handle objects in memory with their address anyway, there's no overhead.

For the specific implications of using the operators, you'll have to look at the assembler your compiler generates.


Example: consider this trivial code, disassembled via 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);
}

The compiler output can far wildly depending on the vendor and optimization settings.

clang 3.8 using -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

Notice that foo and bar are identical. Do other compilers do something similar? Well...

gcc x64 5.4 using -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

Well, if there were any doubt foo and bar are equivalent, a least by the compiler, I think this:

foo(char*):
        jmp     bar(char*)

is a strong argument they indeed are.