What are the implications of returning a pointer as oppose to populating a parameter?

StackOverflow https://stackoverflow.com/questions/21084059

  •  27-09-2022
  •  | 
  •  

Frage

I am learning C and am interested in the 'best-practice' approach to writing functions. Is it preferable to return a pointer to dynamically created memory, or populate a block of memory that has been statically allocated?

Please consider the functions allocatef and populatef as an example

char* allocatef(size_t size){
    char* result = malloc(8*size);
    // do other stuff
    return result;
}

which we can interact with, through main in the following way

int main(void){
    char* data = allocatef(sizeof(int));
    // do stuff
    free(data);
}

As oppose to this approach, where the function expects the data to already be created

void populatef(char* data){
    // do stuff
}

and the memory is statically allocated in main, saving us from a potential memory leak. Perhaps one drawback is the caller is expected to know the exact type of input populatef is expecting.

int main(void){
    char data[8 * sizeof(int)];
    populatef(data);
    // no need to perform a destroy
}

I found some relevant questions here and here, but these concern C++ and performance considerations. I am more interested in memory safety, and the standard behavior among seasoned C programmers.

War es hilfreich?

Lösung

From the point of view of memory safety, there isn't a "best practice" answer that is best for every situation. The whole point of writing in C is that you can choose how to manage your memory instead of doing it the same way all the time.

Coming back to your question, the two versions are pretty similar in that the caller code is ultimately responsible for the memory (initially allocating and ultimately releasing it), as opposed to having your resource be statically allocated of belonging to some global pool or whatever. They also aren't really mutually exclusive. Consider the following alternative:

//functions exported from module A
struct Data * allocate_data();
void initialize_data(Data *);
void deinitialize_data(Data *);
void deallocate_data(Data *);

//module B:
int main(void){
  Data * allocate_data();
  initialize_data(data);
  //do stuff
  deinitialize_data(data);
  deallocate_data(data);
}

In this version we encapsulate the Data behind an abstract data type. A single module implements all operation on the Data datatype, including initialization, alllocation and deallocation and the caller module is responsible for actually choosing when to allocate and deallocate things. The caller module only ever uses pointer to the datatype and never uses the Data struct directly 1

From this point we can get to your first solution by adding an extra convenience functions to allocate and initialize at the same time. This is most useful if you usually want to malloc your data instead of allocating it in the stack and has the extra advantage of making it easier to code complicated initializations.

To get to the second version, its just a matter of making the Data struct part of the public interface. The advantage is that the caller code allocate that struct on the stack rn statically and the disadvantage is that if the caller does that he is responsible for all the allocation logic (for example, making sure that the array size of the allocated memory is the same array size passed to the initialization function).

1 We could enforce this by doing an empty struct Data; declaration in the Data.h header and including the actual struct Data { char * payload; } declaration in the Data.c file.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top