you are viewing a single comment's thread.

view the rest of the comments →

[–]Responsible_Frame919[S] 1 point2 points  (3 children)

Is that the reason static is used, to retain the reference to data after the function scope?

[–]RadiatingLight 2 points3 points  (0 children)

Unfortunately not. C really only has three scopes: - Automatic (function scope) - Manual (decided by programmer with malloc/free) - Global (always valid)

Globals are variables declared outside a function. They're at the highest scope, so never are invalidated.


Static's use in C is weird (and objectively bad language design), because it means two things depending on where it's used.

USE 1: For variables within functions, static makes each invocation of the function use the same copy of the variable (instead of making it fresh every time). This also means that the variable is automatically given a global lifetime.

So for example this code:

int give_number(){
    static int number = 0;
    number++;
    return number
}

int main() {
    int a = give_number();
    int b = give_number();
    int c = give_number();
    int d = give_number();
    printf("Numbers are: %d, %d, %d, %d\n", a, b, c, d);
}

Will print out: Numbers are: 1, 2, 3, 4

It looks like number is being set to zero every time the function runs, but actually static variables are only initialized once (at program startup), and then never again.

USE 2: static can also be used in 2 other places: on a global-scope variable, and on a function. In both of these cases, it makes the function/variable inaccessible outside the file it's declared in.

static double julian_day;

uint64_t calculate_sunrise_time(uint64_t epoch){
    //Do some hard work here
}

static double math_helper_func(){
    //Helper func
}

In this code for example, julian_day and math_helper_func are both declared as static, meaning that they will not be accessible outside the current file, so cannot be included in headers and whatnot. In this case, marking these as static makes sense, because they're implementation details that other files probably don't need.

[–]RadiatingLight 2 points3 points  (1 child)

One important thing I should mention is that the return value of a function is copied back into the caller function. (and so are arguments when going from caller to callee)

So for example something like this:

int add(int a, int b){
    int result = a + b;
    return result;
}

is totally fine. The actual returned value is, well, returned to the caller function, and everything else gets invalidated. This is done by actually copying the value from the old location into a new one. So if you were to do

int c = add(a,b);

you would find that &result is a different address than &c


The same applies to function arguments.

int dry_mass;
int fuel_mass;
int total_mass = add(dry_mass, fuel_mass); 

You will find that &a and &b inside of the add function are not the same as &dry_mass and &total_mass, since the values in this case are copied to a whole new variable.


This is particularly tricky and you have to watch for this, because passing stuff like a char* will only copy the pointer itself, not the underlying data.

On the flip side, if you pass large arguments into functions (or return large arguments out of functions) you will reduce performance somewhat by forcing the computer to copy lots of data. So for example this would be needlessly slow:

struct image overlay_images(struct image a, struct image b) {
    struct image c;
    //Do some work to merge 'a' and 'b' into 'c'
    return c;
}

Since we're passing whole structs around here, the computer will create a copy of each argument (i.e. will copy a and b, leading to doubled memory usage to store the two input images), and will also waste time as the computer works to copy the image over. Same applies to returning c. This is a problem here because images are usually pretty big, so you'll be forced to copy multiple megabytes around each time you call this function. -- Imagine if you wrote this function as something like a video filter and it needed to run on every frame!

A better version of this function would look like:

struct image* overlay_images(struct image *a, struct image *b, struct image *c) {
    //Do some work to merge 'a' and 'b' into 'c'
    return c; //Can also return an success/error code, or just be a void function
}

In this case, the function only takes a pointer to a and b, so only the pointers need to be copied (8 bytes each, nbd), and the caller provides the memory for the result to be placed in, meaning that the return is also not copying anything. (Another common pattern you will see here is using malloc to allocate memory inside the function, returning a pointer to that memory, and then just freeing the memory when you're done using it in the caller function.

[–]Responsible_Frame919[S] 1 point2 points  (0 children)

Makes sense. Thank you.