Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In C, how would I choose whether to return a struct or a pointer to a struct?

Working on my C muscle lately and looking through the many libraries I've been working with its certainly gave me a good idea of what is good practice. One thing that I have NOT seen is a function that returns a struct:

something_t make_something() { ... } 

From what I've absorbed this is the "right" way of doing this:

something_t *make_something() { ... } void destroy_something(something_t *object) { ... } 

The architecture in code snippet 2 is FAR more popular than snippet 1. So now I ask, why would I ever return a struct directly, as in snippet 1? What differences should I take into account when I'm choosing between the two options?

Furthermore, how does this option compare?

void make_something(something_t *object) 
like image 750
Dellowar Avatar asked Oct 21 '16 02:10

Dellowar


People also ask

Should I return a struct or a pointer?

There are two ways of "returning a structure." You can return a copy of the data, or you can return a reference (pointer) to it. It's generally preferred to return (and pass around in general) a pointer, for a couple of reasons. First, copying a structure takes a lot more CPU time than copying a pointer.

When would you use a pointer to a struct?

Pointer to structure holds the add of the entire structure. It is used to create complex data structures such as linked lists, trees, graphs and so on. The members of the structure can be accessed using a special operator called as an arrow operator ( -> ).

Can you return a pointer to a struct in C?

Use Pointer Notation to Return struct From Function The pointer serves as a handle to the object and its size is fixed regardless of the structure stored there. Using pointers to return struct potentially reduces memory traffic and gives code more performance.

Is struct a return type in C?

Return struct from a function Here, the getInformation() function is called using s = getInformation(); statement. The function returns a structure of type struct student . The returned structure is displayed from the main() function. Notice that, the return type of getInformation() is also struct student .


2 Answers

When something_t is small (read: copying it is about as cheap as copying a pointer) and you want it to be stack-allocated by default:

something_t make_something(void);  something_t stack_thing = make_something();  something_t *heap_thing = malloc(sizeof *heap_thing); *heap_thing = make_something(); 

When something_t is large or you want it to be heap-allocated:

something_t *make_something(void);  something_t *heap_thing = make_something(); 

Regardless of the size of something_t, and if you don’t care where it’s allocated:

void make_something(something_t *);  something_t stack_thing; make_something(&stack_thing);  something_t *heap_thing = malloc(sizeof *heap_thing); make_something(heap_thing); 
like image 153
Jon Purdy Avatar answered Sep 19 '22 11:09

Jon Purdy


This is almost always about ABI stability. Binary stability between versions of the library. In the cases where it is not, it is sometimes about having dynamically sized structs. Rarely it is about extremely large structs or performance.


It is exceedingly rare that allocating a struct on the heap and returning it is nearly as fast as returning it by-value. The struct would have to be huge.

Really, speed is not the reason behind technique 2, return-by-pointer, instead of return-by-value.

Technique 2 exists for ABI stability. If you have a struct and your next version of the library adds another 20 fields to it, consumers of your previous version of the library are binary compatible if they are handed pre-constructed pointers. The extra data beyond the end of the struct they know about is something they don't have to know about.

If you return it on the stack, the caller is allocating the memory for it, and they must agree with you on how big it is. If your library updated since they last rebuilt, you are going to trash the stack.

Technique 2 also permits you to hide extra data both before and after the pointer you return (which versions appending data to the end of the struct is a variant of). You could end the structure with a variable sized array, or prepend the pointer with some extra data, or both.

If you want stack-allocated structs in a stable ABI, almost all functions that talk to the struct need to be passed version information.

So

something_t make_something(unsigned library_version) { ... } 

where library_version is used by the library to determine what version of something_t it is expected to return and it changes how much of the stack it manipulates. This isn't possible using standard C, but

void make_something(something_t* here) { ... } 

is. In this case, something_t might have a version field as its first element (or a size field), and you would require that it be populated prior to calling make_something.

Other library code taking a something_t would then query the version field to determine what version of something_t they are working with.

like image 38
Yakk - Adam Nevraumont Avatar answered Sep 21 '22 11:09

Yakk - Adam Nevraumont