5

I want to know if return pointer from a C function is good/bad design? If it is a bad practice, what would be a good practice in the following example:

The question is a continued part of: c function return static variable

in data.h file:

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

typedef struct
{
   int age;
   int number;
} person;

person * getPersonInfo();

in data.c

#include "data.h"
static struct person* person_p = NULL;

person * getPersonInfo()
{
   person_p = (struct person*)malloc(10 * sizeof(struct person));
   return person_p;
}

in main.c

#include "data.h"

int main()
{
   person* pointer = getPersonInfo();
   return 0;
}

basically, the main function in main file needs to get the value of all the elements of array which is pointed by static pointer person_p, if it is not a good practice, then what a good practice should be?

Community
  • 1
  • 1
ratzip
  • 1,571
  • 7
  • 28
  • 53
  • 4
    If you have a function that performs a `malloc`, you should also have a matching function to do `free()`, even if it is just a wrapper. It is bad design to call `free(pointer)` in your `main` - Which you're not doing in the example, thus leaking memory...kinda. – mtijanic Jun 03 '15 at 14:20
  • 7
    Standard Warning : Please [do not cast](http://stackoverflow.com/q/605845/2173917) the return value of `malloc()` and family in `C`. – Sourav Ghosh Jun 03 '15 at 14:21
  • 1
    `malloc` returns a pointer and is a big part of the standard lib. What do you think? – Eregrith Jun 03 '15 at 14:29
  • 1
    How would you get along in any non-trivial program in C without that?? – too honest for this site Jun 03 '15 at 14:33
  • Close voters: Seriously, if people think good/bad program design is "opinion-based" or _"the arts"_, then you need to get out of the garage and get some proper programming education. What's good and bad program design is _not_ opinion-based, there is plenty of research on the topic and there is plenty of consensus among programmers all around the world. If people can't ask program design questions on SO, then I don't know what can be asked here. – Lundin Jun 03 '15 at 17:52

4 Answers4

5

The only reason it is bad is because you don't have any memory managing structure behind it. In your current code, you have a memory leak because you allocate a person struct via malloc() but do not free it.

Consider writing a wrapper function that handles that memory management for you like so:

void freePerson(struct person * personToDelete)
{
    free(personToDelete);
}

Then in your main:

int main()
{
   person* pointer = getPersonInfo();
   freePerson(pointer); // After you are done using it
   return 0;
}

I also have to warn against casting the results of malloc(). In my experience it can result in undefined behavior.

Lawrence Aiello
  • 4,560
  • 5
  • 21
  • 35
  • what is the wrapper looks like, I do not know about it – ratzip Jun 03 '15 at 14:27
  • It's not necessarily bad - it just transfers the responsibility for freeing the allocated memory to the caller – Ishay Peled Jun 03 '15 at 14:29
  • 1
    Which is bad design, because the caller shouldn't be assumed to free those structures. – Lawrence Aiello Jun 03 '15 at 14:37
  • @IshayPeled Unless you are writing your own memory allocation routine or some such, then that is _bad design_ and one of the most common causes for memory leaks in all software released the last decades. – Lundin Jun 03 '15 at 17:37
2

It is bad practice to return pointers to private variables. Also, with the current design, your .c file can only have one instance of the person object.

And almost needless to say, the same code which dynamically allocates an object should also be responsible of freeing it. Code which is written so that some other module in the program is expected to clean up the mess always ends up with memory leaks, per design.

If you are writing an at least somewhat complex data type, where you need to restrict access to private variables, have multiple instances of the struct etc, then it is good practice to use "opaque type" instead. Example (not tested):

// data.h

#ifndef DATA_H 
#define DATA_H

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

typedef struct person person; // opaque type

person* person_create (void);

void person_delete (person* p);

void person_set_age (person* p, int age);
int  person_get_age (const person* p);
// ... and so on, setters/getters

#endif // DATA_H

// data.c

#include "data.h"

struct
{
  int age;
  int number;
} person;    


person* person_create (void)
{
  return malloc(sizeof(struct person));
}

void person_delete (person* p)
{
  free(p);
}

void person_set_age (person* p, int age)
{
  p->age = age;
}

int person_get_age (const person* p)
{
  return p->age;
}

// caller:

#include "data.h"

int main()
{
   person* p = person_create();

   person_set_age(p, 50);
   printf("%d", person_get_age(p));

   person_delete(p);
   return 0;
}

Some things to consider:

  • Always use header guards in h files.
  • Never use empty parameter list () for C functions (but always do in C++).
  • Don't cast the result of malloc in C (but always do in C++).
  • There needs to be some error handling added to this code in case malloc fails.
Lundin
  • 195,001
  • 40
  • 254
  • 396
  • somehow, the data.c is used to keep all the data needed by the program, and then the main function in main file will fetch the needed data from the data.c file, in your example, looks like the data is kept in caller file? And how about if the person is a array? – ratzip Jun 03 '15 at 14:54
  • @ratzip With this approach, everything is allocated by the data.c file and only that file knows what to do with it. The actual data is stored on the heap... not in a file. If you want an array, simply `person* p[n];` and call the create function in a loop. – Lundin Jun 03 '15 at 17:32
0

It's a matter of decision more than everything else.

Both options are valid as long as you're aware of each pro's and con's

Returning pointer pros:

  1. Encapsulation of memory allocation
  2. Support dynamic memory sizes on the fly

Returning pointer cons:

  1. only one possible out parameter
  2. Memory has to be allocated on the heap every time

Accepting pointer as input pros are exactly the opposite:

  1. multiple out parameters
  2. Memory does not have to be allocated on the heap, the caller can use a variable on it's stack to use as output parameter

Accepting pointer as input cons:

  1. No encapsulation of memory allocation
  2. No support for dynamic memory sizes on the fly - sizes must be fixed or pre-specified

In your specific example, I'd use an output parameter as it is fixed size and can easily be called without allocating memory dynamically.

Ishay Peled
  • 2,783
  • 1
  • 23
  • 37
  • what is the encapsulation you mean, this is in C – ratzip Jun 03 '15 at 15:38
  • Encapsulation is a term not specific to OOP - you encapsulate the memory allocation in the function you use to calculate it – Ishay Peled Jun 03 '15 at 15:50
  • "only one possible out parameter" Err... no? C has pointers. Passing parameters by pointers is everyday C programming. Pointer-to-pointers exist pretty much for the sole reason of returning dynamic memory through a parameter. "Memory has to be allocated on the heap every time". No, you can write static memory pools or use alloca or similar. And where memory is allocated is not really related to the program design. – Lundin Jun 03 '15 at 17:41
  • @IshayPeled Encapsulation _is_ a term specific to OOP, but **OO design has nothing to do with language choice**. Some languages have support for OO features, but just because they do, it doesn't necessarily mean that your program turns OO just because you program in a language with such features. Similarly, you can use OO design when writing C programs. http://stackoverflow.com/questions/351733/can-you-write-object-oriented-code-in-c – Lundin Jun 03 '15 at 17:45
  • Lyndon - encapsulation is a term in the English language, please look it up and see what I meant by encapsulating memory allocation. If you return the memo – Ishay Peled Jun 04 '15 at 06:15
0

To be a good design you must have a matching function that frees the allocated memory when it is no longer needed.