Dynamic Memory Allocation with Static Pointer

2019-07-13 00:40发布

问题:

Can somebody please explain to me why the following piece of code works the way it works. Here I have initialized outd as a static pointer in the file code2.c. Then I allocate memory to it dynamically with malloc. Calling it again and again from the main function in a separate file code1.c, it looks the entire array behaves in a static way as it preserves all the values from one function call to another function call even though the memory for the array was allocated dynamically. I was expecting something like segmentation fault.

In code2.c

#include <stdio.h>
#include <stdlib.h>

static double *outd;

void init0(int len)
{
    int i;
    outd=malloc(sizeof(double)*len);
    for (i=0; i<len; i++)
    {
       outd[i]=0.0;
    }
}

void showarray(int len, int iterno)
{
    int i;
    printf("iteration %d, array is \n",iterno);
    for (i=0; i<len; i++)
    {
        outd[i]=outd[i]+iterno;
        printf("%.2f ",outd[i]);
    }
    printf("\n");
}

void deletearray()
{
    free(outd);
}

In code1.c

#include <stdio.h>
#include <stdlib.h>

void init0(int len);
void showarray(int len, int iterno);
void deletearray();

int main(int argc,char* argv[])
{
    int i, len;
    len=5;

    init0(len);

    for (i=0; i<7; i++)
    {
        showarray(len,i);
    }

    deletearray();
}

Compiling,

$(CC) -c -O2 code1.c 
$(CC) -c -O2 code2.c 
$(CC) -o bb5 code1.o code2.o -lm

Running

localhost:testfft avinash$ ./bb5
iteration 0, array is 
0.00 0.00 0.00 0.00 0.00 
iteration 1, array is 
1.00 1.00 1.00 1.00 1.00 
iteration 2, array is 
3.00 3.00 3.00 3.00 3.00 
iteration 3, array is 
6.00 6.00 6.00 6.00 6.00 
iteration 4, array is 
10.00 10.00 10.00 10.00 10.00 
iteration 5, array is 
15.00 15.00 15.00 15.00 15.00 
iteration 6, array is 
21.00 21.00 21.00 21.00 21.00 

回答1:

Please find the technical answer(s) in the comments and other answers.
I provide code (based on your code) for illustrating those good explanations.

To achieve that illustration, I play with all kinds of static (two kinds), local and global variables.
(I am using integers instead of pointers, to keep things easy.
The difference, which might be part of the answer for your prominent puzzling, is only in whether the buffer is changed. There are comments on what does happen and what does not happen related to that.)

code2.c:

#include <stdio.h>
#include <stdlib.h>

// defining a pointer which is only visible from this file,
// to later be initialised to the return value of malloc()
static double *outd;

// defining an int which is only visible from this file,
// to later be initialised to some integer value
static    int  outI;

// define an int which is only visible from this file,
// but has an identically named twin in the other file
// (there be dragons)
static    int unknownTwinI;

// defining a global int which is visible from main()
          int  globalI;

void init0(int len)
{
    int i;

    // initialise the pointer
    // to the address of a buffer reserved for use via this pointer
    outd=malloc(sizeof(double)*len);

    // initialise the memory inside that buffer
    for (i=0; i<len; i++)
    {
       outd[i]=0.0;
    }

    // initialise the int to a value
    outI = 0;

    // initialise the global int to a value
    globalI = 5;

    // initialise one of the two twins
    unknownTwinI = 6;
}

// show the content of the buffer referenced by the pointer
// and the value of the integer
void showarray(int len, int iterno)
{
    int i;

    // make a function local, non-static integer
           int locI =0; // this init happens every time the functin is executed
    // make a function-local, but static integer
    static int inI  =0; // this init happens before the first execution of the function

    printf("iteration %d, array is \n",iterno);
    for (i=0; i<len; i++)
    {
        outd[i]=outd[i]+iterno; // "counting up" array contents
        printf("%.2f ",outd[i]);// show
    }
    outI   = outI  + iterno;    // "counting up" file-local integer value
    printf(" outI:%d", outI);   // show

    inI    = inI   + iterno;    // "counting up" the function-local static integer
    printf(" inI:%d", inI);     // show

    locI   = locI  + iterno;    // "single increase" the function-local integer
    printf(" locI:%d", locI);   // show  

    globalI   = globalI  + iterno;    // "single increase" the function-local integer
    printf(" globalI:%d", globalI);    // show

    unknownTwinI   = unknownTwinI  + iterno;    // "single increase" the function-local integer
    printf(" unknownTwinI:%d", unknownTwinI);    // show

    // Note that nothing here frees the buffer
    // or changes the pointer (which would be highly questionable, thinking of the free() later

    printf("\n");


}

void deletearray()
{
    free(outd);
}

code1.c:

#include <stdio.h>
#include <stdlib.h>

void init0(int len);
void showarray(int len, int iterno);
void deletearray();

// declare the global integer, which is defined in code2.cabs
// (should be in a header.h, 
//  excuse me for taking a shortcut for only having two files to post)
extern int globalI;

// attempt to similarly declare some of the identifiers which cannot be accessed
extern double *outd;
extern    int  outI;
extern    int inI;
extern    int locI;

// define an int which is only visible from this file,
// but has an identically named twin in the other file
// (there be dragons)
static    int unknownTwinI;

int main(int argc,char* argv[])
{
    int i, len;
    len=5;

    // exception of an init outside of init0(),
    // this one targets the twin in THIS file here
    unknownTwinI =0;

    // pointer gets address, buffer gets values
    // integers (those not static to showarray) get values
    init0(len);

    for (i=0; i<7; i++)
    {
        // all kinds of counting gets done
        // (only during the first execution
        //  the local static int initially has the init value)
        showarray(len,i);

        // demonstrating that the global integer is accessable
        globalI = globalI * 2;

        // the showarray outputs the value of the twin in the other file,
        // attempting to resist/undo the cumulative changes done there
        unknownTwinI =0;
        // (resistance is futile)

        // these are forbidden accesses,
        //  with the warnings you get WITHOUT trying to declare them

        // outd=NULL; // 'outd' undeclared (first use in this function)
        // outI=0;    // 'outI' undeclared (first use in this function)
        // inI = 0;   // 'inI' undeclared (first use in this function)
        // locI =0;   // 'locI' undeclared (first use in this function)



        // these are the forbidden accesses again,
        //   with the warnings you get WITH trying to declare them

        // outd=NULL; // undefined reference to `outd'
        // outI=0;    // undefined reference to `outI'
        // inI = 0;   // undefined reference to `inI'
        // locI =0;   // undefined reference to `locI'
    }

    deletearray();

    return 0;
}

Output:

iteration 0, array is
0.00 0.00 0.00 0.00 0.00  outI:0 inI:0 locI:0 globalI:5 unknownTwinI:6
iteration 1, array is
1.00 1.00 1.00 1.00 1.00  outI:1 inI:1 locI:1 globalI:11 unknownTwinI:7
iteration 2, array is
3.00 3.00 3.00 3.00 3.00  outI:3 inI:3 locI:2 globalI:24 unknownTwinI:9
iteration 3, array is
6.00 6.00 6.00 6.00 6.00  outI:6 inI:6 locI:3 globalI:51 unknownTwinI:12
iteration 4, array is
10.00 10.00 10.00 10.00 10.00  outI:10 inI:10 locI:4 globalI:106 unknownTwinI:16
iteration 5, array is
15.00 15.00 15.00 15.00 15.00  outI:15 inI:15 locI:5 globalI:217 unknownTwinI:21
iteration 6, array is
21.00 21.00 21.00 21.00 21.00  outI:21 inI:21 locI:6 globalI:440 unknownTwinI:27


回答2:

Yunnosch's answer explains the effects of different declarations by example quite well, but I want to add some background from the language specification because I think this helps understanding a lot.

  • Identifiers in C have scopes. The scope determines the area in which this identifier refers to the object associated with it. In C, the scope of an identifier is the enclosing pair of curly braces ({ ... }).

    if (1) {
        int i = 2;
        // i refers to an object holding the value 2 here
    }
    // i doesn't refer to any object
    

    Identifiers outside any braces have file scope, they refer to the object in the whole source file.

  • Objects in C can have different storage durations. The storage duration determines for how long an object is alive and can be accessed. You should know about the following:

    • automatic: The object lives as long as the execution is inside of its scope. This is the default for any variable not in the file scope.
    • static: The object lives for the whole execution time of the program. This is the default for a declaration in file scope.
    • dynamic: The object is created by allocation (malloc() and friends) and lives until it is manually deallocated (free()).
  • Finally, Identifiers can have different linkage. This determines the visibility between different translation units. An identifier with external linkage is visible outside of its own translation unit, one with internal linkage is not.

    The default linkage for file scoped identifiers is external.


With this theoretical background, let's see what static means. static is a storage class and sets the storage duration to static and the linkage to internal.

example1.c:

static int i;
// static storage duration, internal linkage

int foobar(void) {
    static int j = 5;
    // static storage duration, internal linkage
    return ++j;
    // returns one more at each call, starting from 6
}

vs example2.c:

int i;
// static storage duration, external linkage

int foobar(void) {
    int j = 5;
    // automatic storage duration, internal linkage
    return ++j;
    // always returns 6, because with automatic storage duration,
    // j refers to a NEW object every time the function is entered
}

Concerning your code in the question: The pointer has static storage duration, so it lives for the whole program. Whatever you malloc() lives as long as you don't free() it (see above: dynamic storage duration), so you're perfectly fine with this code.



回答3:

The meaning of static in C (and in other languages too) is different if it's used for functions, global variables or local variables . this keyword does not indicate whether a variable is allocate statically or dinamically, if you use pointers you actually choose the dynamic allocation.

-With local variables the static keyword changes the lifetime of that variable; but if you call a malloc on a static pointer, this one will change its value. Here a example:

void myfunction(){
static int* p = NULL;

if (p == NULL)
    p = (int*) malloc(sizeof(int) * 4);
else
{
    *(p) = 1;
    printf("address: %d, first element: %d", p, p[0]);
}
}

int main()
{
    myfunction();
    myfunction();

    return 0;
}

Only the second myfunction will print the address and the first element. (I know I did not call the free)

  • If static is instead used with function or with global variables, the meaning changes completely: in this case it changes the visibility of that function or global variable. As you have seen in the comments, static functions are only visible by other functions in the same translation unit (the object file). For global static variable is almost the same.