Home > Software engineering >  Pass a pointer instead of return new variable in C and Go?
Pass a pointer instead of return new variable in C and Go?

Time:12-06

Why is it convention in C and Go to pass a pointer to a variable and change it rather return a new variable with the value?

In C:

#include <stdio.h>

int getValueUsingReturn() {
    int value = 42;
    return value;
}

void getValueUsingPointer(int* value ) {
    *value = 42;
}

int main(void) {
  int valueUsingReturn = getValueUsingReturn();
  printf("%d\n", valueUsingReturn);

  int valueUsingPointer;
  getValueUsingPointer(&valueUsingPointer);
  printf("%d\n", valueUsingPointer);
  return 0;
}

In Go:

package main

import "fmt"

func getValueUsingReturn() int {
    value := 42
    return value
}

func getValueUsingPointer(value *int) {
    *value = 42
}

func main() {
    valueUsingReturn := getValueUsingReturn()
    fmt.Printf("%d\n", valueUsingReturn)

    var valueUsingPointer int
    getValueUsingPointer(&valueUsingPointer)
    fmt.Printf("%d\n", valueUsingPointer)
}

It there any performance benefits or restrictions in doing one or the other?

CodePudding user response:

First off, I don't know enough about Go to give a judgement on it, but the answer will apply in the case of C.

If you're just working on primitive types like ints, then I'd say there is no performance difference between the two techniques.

When structs come into play, there is a very slight advantage of modifying a variable via pointer (based purely on what you're doing in your code)

#include <stdio.h>

struct Person {
    int age;
    const char *name;
    const char *address;
    const char *occupation;
};

struct Person getReturnedPerson() {
    struct Person thePerson = {26, "Chad", "123 Someplace St.", "Software Engineer"};
    return thePerson;
}

void changeExistingPerson(struct Person *thePerson) {
    thePerson->age = 26;
    thePerson->name = "Chad";
    thePerson->address = "123 Someplace St.";
    thePerson->occupation = "Software Engineer";
}

int main(void) {
  struct Person someGuy = getReturnedPerson();
  

  struct Person theSameDude;
  changeExistingPerson(&theSameDude);
  
  
  return 0;
}

GCC x86-64 11.2

With No Optimizations

Returning a struct variable through the function's return is slower because the variable has to be "built" by assigning the desired values, after which, the variable is copied to the return value.

When you're modifying a variable by pointer indirection, there is nothing to do except write the desired values to the memory addresses (based off the pointer you passed in)

.LC0:
        .string "Chad"
.LC1:
        .string "123 Someplace St."
.LC2:
        .string "Software Engineer"
getReturnedPerson:
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-40], rdi
        mov     DWORD PTR [rbp-32], 26
        mov     QWORD PTR [rbp-24], OFFSET FLAT:.LC0
        mov     QWORD PTR [rbp-16], OFFSET FLAT:.LC1
        mov     QWORD PTR [rbp-8], OFFSET FLAT:.LC2
        mov     rcx, QWORD PTR [rbp-40]
        mov     rax, QWORD PTR [rbp-32]
        mov     rdx, QWORD PTR [rbp-24]
        mov     QWORD PTR [rcx], rax
        mov     QWORD PTR [rcx 8], rdx
        mov     rax, QWORD PTR [rbp-16]
        mov     rdx, QWORD PTR [rbp-8]
        mov     QWORD PTR [rcx 16], rax
        mov     QWORD PTR [rcx 24], rdx
        mov     rax, QWORD PTR [rbp-40]
        pop     rbp
        ret
changeExistingPerson:
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-8], rdi
        mov     rax, QWORD PTR [rbp-8]
        mov     DWORD PTR [rax], 26
        mov     rax, QWORD PTR [rbp-8]
        mov     QWORD PTR [rax 8], OFFSET FLAT:.LC0
        mov     rax, QWORD PTR [rbp-8]
        mov     QWORD PTR [rax 16], OFFSET FLAT:.LC1
        mov     rax, QWORD PTR [rbp-8]
        mov     QWORD PTR [rax 24], OFFSET FLAT:.LC2
        nop
        pop     rbp
        ret
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 64
        lea     rax, [rbp-32]
        mov     rdi, rax
        mov     eax, 0
        call    getReturnedPerson
        lea     rax, [rbp-64]
        mov     rdi, rax
        call    changeExistingPerson
        mov     eax, 0
        leave
        ret

With Slight Optimization

However, most compilers today can figure out what you're trying to do here, and will equalize the performance between the two techniques.

If you want to be absolutely stingy, passing pointers is still slightly faster by a few clock cycles at best.

In returning a variable from the function, you still have to at least set the address of the return value.

        mov     rax, rdi

But in passing the pointer, not even this is done.

But other than that, the two techniques have no performance difference.

.LC0:
        .string "Chad"
.LC1:
        .string "123 Someplace St."
.LC2:
        .string "Software Engineer"
getReturnedPerson:
        mov     rax, rdi
        mov     DWORD PTR [rdi], 26
        mov     QWORD PTR [rdi 8], OFFSET FLAT:.LC0
        mov     QWORD PTR [rdi 16], OFFSET FLAT:.LC1
        mov     QWORD PTR [rdi 24], OFFSET FLAT:.LC2
        ret
changeExistingPerson:
        mov     DWORD PTR [rdi], 26
        mov     QWORD PTR [rdi 8], OFFSET FLAT:.LC0
        mov     QWORD PTR [rdi 16], OFFSET FLAT:.LC1
        mov     QWORD PTR [rdi 24], OFFSET FLAT:.LC2
        ret
main:
        mov     eax, 0
        ret

CodePudding user response:

I think the short answer to you question (At least for C, I am not familiar with GO internals) is that C functions are pass by value and generally also return by value so data objects must be copied and people worried about the performance of all the copying. For large objects or objects that are complex in their depth (containing pointers to other stuff) it is often just more efficient or logical for the value being copied to be a pointer so the function can "operate" on the data without needing to copy it. That being said, modern compilers are pretty smart about figuring out stuff like whether the parameter data will fit in registers or efficiently copying returned structures. Bottom line is for modern C code do what seems best for your application or what is clearest to you. Avoid premature optimization if it detracts from readability at least in the beginning. Also Compiler Explorer (https://godbolt.org/) is your friend if you want to examine the effect of different styles, especially in light of optimization.

  • Related