0xRyN
0xRyN

Reputation: 872

C, Malloc, Pointers and Context of Exectution

Edit : Read this first : https://stackoverflow.com/a/8800541/14795595

I have this code :

#include <assert.h>
#include <stddef.h>
#include <string.h>
#include <stdlib.h>
typedef struct{
  double x;
  double y;
} point;

point *inserer_point( unsigned *len, point chemin[], point p, unsigned pos ){
  assert( pos <= *len );
  printf("%d",*len);

  if( chemin == NULL )
    assert( *len == 0 && pos == 0 );

  chemin = realloc( chemin,  (*len + 1) * sizeof( point ) );
  assert( chemin );

  memmove( chemin + pos + 1, chemin + pos, sizeof(point)*( *len - pos ) );
  chemin[pos] = p;
  (*len)++;

  return chemin;
}

int main(){
  point *c=NULL;
  unsigned l = 0;

  c = inserer_point( &l, c, (point){.x = 4, .y = 6}, 0);
  c = inserer_point( &l, c, (point){.x = 5, .y = 7}, 0);
  c = inserer_point( &l, c, (point){.x = 6, .y = 8}, 2);
  c = inserer_point( &l, c, (point){.x = -7, .y = -9}, 1);
  c = inserer_point( &l, c, (point){.x = -4, .y = -6}, 4);
  c = inserer_point( &l, c, (point){.x = -44, .y = 9}, 4);
  c = inserer_point( &l, c, (point){.x = -444, .y = -69}, 2);
         
}

As you can see, l is declared in main without a malloc, a calloc or a realloc. Which means it is declared in stack. And we don't have control over it.

It should be read only and can only be modified in the context of execution (in the main function).

However, we send a pointer to l in the other function as *len.

And then we increment len (*len++) at the bottom of the function.

As I said, it should not be possible since it is not on the heap and should be read only.

But this code works and len gets incremented.

Am I wrong about memory access ? What did I not get ? Thank you !

EDIT 2:

This is pretty similar returns SEGMENTATION FAULT. Why ?

void disp (int t[], int a, int b) {
    for (int i = a; i < b - 1; i++) {
        printf ("%d, ", t[i]);
        }
    printf("%d\n", t[b - 1]);
}

int *build (int a, int n) {
    int t[n];
    for (int i = 0; i < n; i++) {
        t[i] = a + i;
    }
    printf ("t : %p : ", t);
    disp (t, 0, 15);
    return t;
}


int main(void){
    printf ("\nbuild tab\n");
    int *t = build (0, 15);
    printf ("tab : %p\n", t);
    disp (t, 0, 15); // SEG_FAULT!
    return 0;

}

Upvotes: 1

Views: 207

Answers (6)

Keith Thompson
Keith Thompson

Reputation: 263307

The key concepts here are scope and lifetime.

Here's a simpler example:

#include <stdio.h>

void func(int *param) {
    *param = 20;
}

int main(void) {
    int n = 10;
    printf("Before, n = %d\n", n);
    func(&n);
    printf("After, n = %d\n", n);
}

We have an object n of type int defined locally in main. Its storage class is automatic, which typically means it's allocated on the stack.

The scope of the identifier n is the region of program text in which the name n is visible. It extends from the definition of n to the closing } of the main function.

The lifetime of the object named n is the period of time during program execution in which the object exists. It begins when execution enters the main function and ends when main completes.

(The lifetime of an object created by malloc extends from the successful malloc call until the object is deallocated, for example by passing its address to free, or until the program terminates. Such an object has no scope because it has no name; it can only be referred to indirectly.)

Inside the body of func, the name n is out of scope, so if I wrote n = 42; inside func I'd get a compile-time error. The name is not visible. However, while func is executing, the object named n exists, and can be referred to indirectly (though not by its name).

The object n is not read-only. If you wanted it to be, you could define it with the const keyword. You'd also have to define param as const int *param, because it's illegal to pass a pointer to a const object to a function that takes a pointer to a non-const object.

There is no reason to expect the above program (or yours, as far as I can tell) to suffer a segmentation fault, since no objects are accessed outside their lifetimes.

Passing a pointer to an object to a function so the function can modify that object is perfectly valid, and is very common.

It should be read only and can only be modified in the context of execution (in the main function).

That's just incorrect. It's not read-only, and it can be modified at any time during its lifetime. In this case, it's modified via a pointer.

UPDATE: I see you've added code that does produce a segmentation fault. Here's an abbreviated summary of the relevant part:

int *build (int a, int n) {
    int t[n];
    /* ... */
    return t;
}

t is a VLA (variable length array), defined locally in the build function. It has automatic storage duration, meaning that its lifetime is ends when build returns. The return t; statement doesn't return the array object; it returns a pointer to it. That pointer becomes a dangling pointer when the caller (main) attempts to use it. In main you have:

int *t = build (0, 15);

t points to an object that no longer exists.

Your original code did not do anything like that. Your inserer_point function returns a pointer, but it points to an object that was created in main, so it still exists when main receives the pointer to it. (And main doesn't do anything with the pointer other than assigning it to an object which is never used.)

C does not support passing arrays as parameters or returning them from functions, but a lot of the syntax makes it look like it does. Read section 6 of the comp.lang.c FAQ.

Upvotes: 1

dbush
dbush

Reputation: 223972

You seem to be confused regarding the difference between the scope and lifetime of an object.

The scope of an object designates where an object can be accessed by its declared name. For a local variable, that starts at the point it is declared until the block containing it ends, and only within that block.

The lifetime of an object designates how long the memory set aside for it is valid for. For a local variable, that starts and the beginning of the block where it is declared and ends when that block ends, and includes any functions that may be called within that block.

In your first example, l is a local variable in the main function, so its lifetime starts when main starts and ends when main returns, and is still valid when other functions are called within main. That's why you can pass &l to a function and dereference the pointer safely.

In your second example, t is an array local to the build function. Its lifetime starts when the build function is entered and ends when build returns. You then return t from the function. This actually returns a pointer to the first member of the array. So now your main function has a pointer to the first element of t, but since build returned that means the lifetime of t has ended rendering the returned pointer indeterminate, and attempting to dereference it triggers undefined behavior which in your case causes a crash.

Upvotes: 1

John Bollinger
John Bollinger

Reputation: 180306

As you can see, l is declared in main without a malloc, a calloc or a realloc. Which means it is declared in stack. And we don't have control over it.

That l is declared inside main means that it has automatic storage duration and that the scope the identifier l ends at the end of main. Whether such a variable lives on the stack, or whether there even is a stack, is a detail of your C implementation. It is true, however, that you don't have control over where it is allocated.

It should be read only

No. I don't see what gives you that idea.

and can only be modified in the context of execution (in the main function).

"can be modified" is inconsistent with "read only", but of course I have already denied your assertion about the object being read only.

Now also no, nothing about the declaration of l implies that the object it identifies can be modified only by code in main. The limitation here is that the object can be accessed via its identifier only within the scope of the identifer, which is limited to main. But via its identifier, if it even has one, is not the only way to access an object.

However, we send a pointer to l in the other function as *len.

You obtain a pointer via the address-of operator: &l. Another way to access an object is via a pointer to it. C does not distinguish between objects with different storage durations in this regard (as long as objects are accessed only during their lifetimes), nor does the scope of an identifier come into it other than for obtaining a suitable pointer in the first place.

Having passed that pointer value to your function, it being received as the value of parameter len, in that function the expression *len designates the same object that l designates in main.

And then we increment len (*len++) at the bottom of the function.

Yes. No problem with that.

As I said, it should not be possible since it is not on the heap and should be read only.

No. Supposing that we stipulate a stack / heap memory arrangement, which indeed is very common, you can obtain a pointer to an object on the stack. That does not move it to the heap, nor make a copy of it on the heap. It just obtains the address of that object, wherever in memory it may be. You would probably be better off forgetting about (this kind of) stack and heap, since again, they are not C language concepts at all.

Moreover, even if you passed a pointer to an object on the heap, there is no reason to think that such an object would be read only.

But this code works and len gets incremented.

Yes.

Am I wrong about memory access ? What did I not get ?

Yes, apparently you are pretty wrong. Stack and heap storage are not C concepts. Pointers can point to any object in the program, stack / heap considerations notwithstanding. Taking the address of an object does not copy or move the object. Nothing about an object being on the heap has anything to do with whether it is read only. Neither does identifier scope.

Upvotes: 0

Blindy
Blindy

Reputation: 67380

I learned that variables not using malloc are stored in stack. And we can't manage stack except in the context of execution.

It's always difficult to communicate basic concepts when one side makes up words like "context of execution" when things have proper names (closest would be "scope" in this case).

I believe the missing gap in knowledge here is that the scope of l is the scope it belongs to (ie the closest pair of braces, in this case the function main), as well as every single function's scope called from within this scope.

And this isn't an arbitrary rule, it makes sense when you consider that the stack gets expanded as you call functions, and only reduced when you exit functions. Your l is valid until the stack frame that it belongs to is no longer valid, ie until you exit main. It gets a little more complicated when you have nested scopes within your function scope, but in this case you do not.

Upvotes: 1

Vlad from Moscow
Vlad from Moscow

Reputation: 310980

You passed the object l by reference to the function inserer_point.

c = inserer_point( &l, c, (point){.x = 4, .y = 6}, 0);
                   ^^

In C passing by reference means passing an object indirectly through a pointer to it.

So dereferencing the pointer within the function you have a direct access to the pointed object and can change it.

Here is a simple demonstrative program.

#include <stdio.h>

void f( int *px )
{
    *px = 20;
}

int main(void) 
{
    int x = 10;
    
    printf( "Before calling f x is equal to %d\n", x );
    
    f( &x );
    
    printf( "After  calling f x is equal to %d\n", x );

    return 0;
}

The program output is

Before calling f x is equal to 10
After  calling f x is equal to 20

That is it is unimportant where an object is defined (allocated). You can use a pointer to the object to change it by means of dereferencing the pointer that gives you an access to the memory where the object is present.

Upvotes: 1

Clarus
Clarus

Reputation: 2338

C doesn't enforce any memory restrictions. Some compilers may generate warnings if you define a pointer as a const, and then try to modify it but that's about it. You are free to modify the heap/stack/anything, and the language is happy to allow it (although you may get a segmentation fault).

The whole point of languages like Rust is that they provide a C-like environment that is memory safe. If you want memory safety, don't use C.

Upvotes: -2

Related Questions