In our code base, I encountered some C code that I am not able to understand why it works.
Pretty sure implements some pattern found on the internet. Ideally this code should emulate some object-oriented pattern from C++, and it is used to create queues.
Here is (part of) the code for the declaration (.h) of the queue module:
struct Queue_t
{
uint8_t Queue[MAX_QUEUE_SIZE];
uint32_t Head;
uint32_t Tail;
uint16_t Counter;
Queue_return_t (*Push)(struct Queue_t * Queue, uint8_t * NewElement, uint16_t ElementSize);
Queue_return_t (*Pop)(struct Queue_t * Queue, Queue_Element_t *RetElement);
Queue_return_t (*Flush)(struct Queue_t * Queue);
};
extern const struct QueueClass {
struct Queue_t (*new)( void );
} Queue_t;
struct Queue_t new( void );
Here is (part of) the code for the implementation (.c) of the queue module:
struct Queue_t new( void )
{
struct Queue_t NewQueue;
[...]
NewQueue.Push = &QueueManager_Push;
NewQueue.Pop = &QueueManager_Pop;
NewQueue.Flush = &QueueManager_Flush;
return NewQueue;
}
const struct QueueClass Queue_t={.new=&new};
then the usage in the code is the following:
struct Queue_t Output_Queue;
Output_Queue = Queue_t.new();
[...]
RetQueue = Output_Queue.Pop(&Output_Queue,Output_Queue_elem);
Now, we switched to a more straightforward queue implementation. Still I am not able to grasp what is going on in this code.
As stated in the title, my problem is in the "new" function, where a struct Queue_t
is declared in a local scope and then returned.
As further information, in the project that used this "queue" module there was no dynamic memory allocation, hence no heap, free or malloc.
Everything worked really smooth, I would expect the code to crash as soon as the stack for the referenced object is freed and the pointer to the structure is accessed.
Also, the compiler used was IAR and is not complaining (this code was used on a uC).
Maybe the const
qualifier is involved?
Any hint on what is going on?
It is perfectly legal to return a value declared inside a function. The problem only arises if you return a pointer to a value declared inside a function, and then try to access it.
The new()
function you show is equivalent to:
int foo(void) {
int a = 123;
return a;
}
There is no problem here, you're returning a value which is copied to the destination when doing:
int x;
x = foo(); // copy happens here
Using x
after this is not accessing the (now vanished) stack of foo
, since x
is defined outside foo
and was copied when the function returned.
Doing this with a struct
is no different, a struct
is still treated as a single value, the only real difference is in the generated machine code, since copying a structure takes more effort (you need to copy each field).
The problem, again, only arises when you return a pointer to a locally defined variable and try using it outside:
int *foo(void) {
int a = 123;
return &a; // never do this!
}
int main(void) {
int *x;
x = foo();
return *x; // OUCH!
}
Now dereferencing x
(both for reading and for writing) means trying to access the (now vanished) stack of foo
, and this is of course a problem.