Returning a pointer to a static buffer

837 Views Asked by At

In C, on a small embedded system, is there any reason not to do the following?

const char * filter_something(const char * original, const int max_length)
{
    static char buffer[BUFFER_SIZE];
    // Checking inputs for safety omitted

    // Copy input to buffer here with appropriate filtering, etc.
    return buffer;
}

This is essentially a utility function the source is flash memory which may be corrupted, we do a kind of "safe copy" to make sure we have a null terminated string. I chose to use a static buffer and make it available read only to the caller.

A colleague is telling me that I am somehow not respecting the scope of the buffer by doing this, to me it makes perfect sense for the use case we have.

Is there a reason not to do this?


Many thanks to all who responded. You have generally confirmed my ideas on this, which I am grateful for. I was looking for major reasons not to do this, I don't think that there are any. To clarify a few points:

  1. rentrancy/thread safety is not a concern. It is a small (bare metal) embedded system with a single run loop. This code will not be called from ISRs, ever.

  2. in this system we are not short on memory, but we do want very predictable behavior. For this reason I prefer declaring an object like this statically, even though it might be a little "wasteful". We have already had issues with large objects declared carelessly on the stack, which caused intermittent crashes (now fixed, but it took a while to diagnose). So in general, I am preferring static allocation, simply to have very predictability, reliability, and less potential issues downstream.

So basically it's a case of taking a certain approach for a specific system design.

5

There are 5 best solutions below

4
dbush On

It is true that the identifier buffer only has scope local to the block in which it is declared. However, because it is declared static, its lifetime is that of the full program.

So returning a pointer to a static variable is valid. In fact, many standard functions do this such as strtok and ctime.

The one thing you need to watch for is that such a function is not reentrant. For example, if you do something like this:

printf("filter 1: %s, filter 2: %s\n", 
       filter_something("abc", 3), filter_something("xyz", 3));

The two function calls can occur in any order, and both return the same pointer, so you'll get the same result printed twice (i.e. the result of whatever call happens to occur last) instead of two different results.

Also, if such a function is called from two different threads, you end up with a race condition with the threads reading/writing the same place.

2
cup On

It really depends on how filter_something is used. Take the following as an example

#include <stdio.h>
#include <string.h>

const char* filter(const char* original, const int max_length)
{
    static char buffer[1024];

    memset(buffer, 0, sizeof(buffer));
    memcpy(buffer, original, max_length);
    return buffer;
}

int main()
{
    const char *strone, *strtwo;
    char deepone[16], deeptwo[16];

    /* Case 1 */
    printf("%s\n", filter("everybody", 10));

    /* Case 2 */
    printf("%s %s %s\n", filter("nobody", 7), filter("somebody", 9), filter("anybody", 8));

    /* Case 2 */
    if (strcmp(filter("same",5), filter("different", 10)) == 0)
        printf("Strings same\n");
    else
        printf("Strings different\n");

    /* Case 3 - Both of these end up with the same pointer */
    strone = filter("same",5);
    strtwo = filter("different", 10);
    if (strcmp(strone, strtwo) == 0)
        printf("Strings same\n");
    else
        printf("Strings different\n");

    /* Case 4 - You need a deep copy if you wish to compare */
    strcpy(deepone, filter("same", 5));
    strcpy(deeptwo, filter("different", 10));
    if (strcmp(deepone, deeptwo) == 0)
        printf("Strings same\n");
    else
        printf("Strings different\n");
}

The output when gcc is used is

everybody
nobody nobody nobody
Strings same
Strings same
Strings different.
  1. When filter is used by itself, it behaves quite well.
  2. When it is used multiple times in an expression, the behaviour is undefined there is no telling what it will do. All instances will use the contents the last time the filter was executed. This depends on the order in which the execution was performed.
  3. If an instance is taken, the contents of the instance will not stay the same as when the instance was taken. This is also a common problem when C++ coders switch to C# or Java.
  4. If a deep copy of the instance is taken, then the contents of the instance when the instance was taken will be preserved.

In C++, this technique is often used when returning objects with the same consequences.

0
Eric Postpischil On

Pro

  1. The behavior is well defined; the static buffer exists for the duration of the program and may be used by the program after filter_something returns.

Cons

  1. Returning a static buffer is prone to error because people writing calls to the routines may neglect or be unaware that a static buffer is returned. This can lead to attempts to use multiple instances of the buffer from multiple calls to the function (in the same thread or different threads). Clear documentation is essential.

  2. The static buffer exists for the duration of the program, so it occupies space at times when it may not be needed.

1
perencia On

Just to add to the previous answers, I think the problem, in a more abstract sense, is to make the filtering result broader in scope than it ought to be. You introduce a 'state' which seems useless, at least if the caller's intention is only to get a filtered string. In this case, it should be the caller who should create the array, likely on the stack, and pass it as a parameter to the filtering method. It is the introduction of this state that makes possible all the problems referred to in the preceding responses.

0
Lundin On

From a program design, it's frowned upon to return pointers to private data, in case that data was made private for a reason. That being said, it's less bad design to return a pointer to a local static then it is to use spaghetti programming with "globals" (external linkage). Particularly when the pointer returned is const qualified.

One general issue with staticvariables, that may or may not be a problem regardless of embedded or hosted system is re-entrancy. If the code needs to be interrupt/thread safe, then you need to implement means to achieve that.

The obvious alternative to it all is caller allocation and you've got to ask yourself why that's not an option:

void filter_something (size_t size, char dest[size], const char original[size]);

(Or if you will, [restrict size] on both pointers for a mini-optimization.)