Romain-p
Romain-p

Reputation: 465

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);
}

Upvotes: 1

Views: 118

Answers (4)

trexxet
trexxet

Reputation: 338

AFAIK, in x86 and x64 your variables are stored in memory (if not stated with register keyword) and accessed by pointers. const int foo = 5 equal to foo dd 5 and check_one(*foo) equal to push dword [foo]; call check_one.

If you create additional variable c, then it looks like:

c resd 1
...
mov eax, [foo]
mov dword [c], eax ; Variable foo just copied to c
push dword [c]
call check_one

And nothing changed, except additional copying and memory allocation. I think that compiler's optimizer deals with it and makes both cases as fast as it is possible. So you can use more readable variant.

Upvotes: 0

smac89
smac89

Reputation: 43148

tldr;

If you are programming in C, then the & operator is used to obtain the address of a variable and * is used to get the value of that variable, given it's address.


This is also the reason why in C, when you pass a string to a function, you must state the length of the string otherwise, if someone unfamiliar with your logic sees the function signature, they could not tell if the function is called as bar(&some_char) or bar(some_cstr).

To conclude, if you have a variable x of type someType, then &x will result in someType* addressOfX and *addressOfX will result in giving the value of x. Functions in C only take pointers as parameters, i.e. you cannot create a function where the parameter type is &x or &&x

Also your examples can be rewritten as:

check_one(str[0])
check_two(str[0])

Upvotes: 0

Marcus Müller
Marcus Müller

Reputation: 36352

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.

Upvotes: 4

John Bode
John Bode

Reputation: 123488

In C, there's no runtime cost associated with either the unary & or * operators; both are evaluated at compile time. So there's no difference in runtime between

check_one(*str)
check_two(*str)

and

 char c = *str;
 check_one( c );
 check_two( c );

ignoring the overhead of the assignment.

That's not necessarily true in C++, since you can overload those operators.

Upvotes: 2

Related Questions