13

Disclaimer: This question is strictly academic. The example I'm about to give is probably bad style.

Suppose in C I write a subroutine of this form:

char *foo(int x)
{
    static char bar[9];

    if(x == 0)
        strcpy(bar, "zero");
    else
        strcpy(bar, "not zero"),

    return bar;
}

Then, elsewhere, I use foo as follows:

printf("%i is %s\n", 5, foo(5));

My mental model of pointers and static variables predicts that, in practice, the output of this printf will be

5 is not zero

...but is this actually required by the C standard, or am I in nasal demon territory?

To make things even worse, what about something like

strcpy(foo(5), "five");

My mental model says this should "work" unless it's explicitly illegal, though it's somewhat pointless since it doesn't affect the output of foo. But again, is this actually defined by the standard?

  • 2
    For your last example, it is "legal", but I'm not sure it's "moral". Might as well have a plain old global if everyone can modify it. (And be careful with threads.) – Mat Apr 03 '12 at 18:11

5 Answers5

11

What you've written is OK; there are no nasal demons awaiting you. Even the strcpy() example is 'OK' because you are not trampling beyond the bounds of the array. That 'OK' is in quotes because it is not a good idea, but as written, there is no out of bounds memory access and so no undefined behaviour. The static data in the function is there throughout the program's lifetime, containing the last value that was written to it.

There could be problems if you try:

printf("%i is %s but %i is %s\n", 5, foo(5), 0, foo(0));

You will get the wrong answer for one of the two numbers, but it is not defined which will be the wrong answer.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • 2
    The variable in the function can only hold either `not zero` or `zero` at the time when `printf()` is called (after the two calls to `foo()` are complete). So the same string will be printed for both 0 and 5, and since one of them is zero and the other is not, it doesn't matter whether the string in the function contains `zero` or `not zero`; it is wrong for one of the two numbers. If you want to be fancy and have them both correct, you'd revise the function: `static const char bar[9] = "not zero"; if (x == 0) return &bar[4]; else return &bar[0];` and suddenly both will be fine. – Jonathan Leffler Apr 03 '12 at 18:11
  • 1
    @0A0D Because that function has a side-effect. – cnicutar Apr 03 '12 at 18:12
  • The part I am not understanding is why the same two strings will be printed for 0 and 5. –  Apr 03 '12 at 18:16
  • 0A0D: Because foo(5) and foo(0) are evaluated before the function call, and they both return a pointer to the same string which they both change. On actually entering printf, only the later change still exists. – jjrv Apr 03 '12 at 18:34
  • Thanks for the additional detail on problems with printf! So it's explicitly required that printf evaluate all expressions before printing anything, but not that it do so in any particular order? I would have assumed either both or neither of these were defined. –  Apr 03 '12 at 19:15
  • 1
    The two function calls could even be executed in parallel and there could be a race on the access to the `static` buffer such that the print out would be a mixture of the two strings. Really bad style in the particular case because you could avoid it all together for your example by just returning the pointers to the string literals. Better declare the return type of the function `char const *`, though. – Jens Gustedt Apr 03 '12 at 19:34
  • 1
    @SunAvatar: yes, the arguments to `printf()` are all evaluated before `printf()` is called, and there's a sequence point before the call so all side effects (if there are any) are in place. However, there is nothing in the standard that says "the arguments shall be evaluated left-to-right or right-to-left"; it is up to the compiler to decide which order to evaluate the arguments in. You know that `foo()` will be called twice, but that's all. And the 'last one in' gets to control the contents of the string that `printf()` will actually print. – Jonathan Leffler Apr 03 '12 at 19:34
4

Well, since you want a quote from the standard:

The lifetime of an object is the portion of program execution during which storage is guaranteed to be reserved for it.

An object whose identifier is declared without the storage-class specifier _Thread_local, and either with external or internal linkage or with the storage-class specifier static, has static storage duration. Its lifetime is the entire execution of the program and its stored value is initialized only once, prior to program startup.

cnicutar
  • 178,505
  • 25
  • 365
  • 392
1

Under any condition, foo() returns a pointer to a static variable, whose lifetime is permanent (i.e. it lives from when control passes over it for the first time until the end of the program). This is perfectly fine.

The logic of your code is less fine; for instance, the function is not re-entrant and far from thread-safe, and of course the unguarded string operation is outright suicide.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • Well, the unguarded string operation is somewhat evil since it makes it easy to break things later, but shouldn't it be okay since I only ever use it for literal strings? Even from outside the function, if I already know that `foo` returns a pointer to a string that I don't have to free, and that that string is sometimes `"not zero"`, then I can infer that the string is statically allocated and I can fit `"five"` into it. –  Apr 03 '12 at 19:22
  • You have to make sure that you never write a string longer than seven characters to the string. Maybe not "suicide" (I think I misread your code as `strcat` rather than `strcpy`), but its needlessly dangerous nonetheless. You can put `strncpy` in there with `sizeof bar`. – Kerrek SB Apr 03 '12 at 22:02
0

Nothing wrong with it. Better style will be

 const char *foo(int x);

Then you can't strcpy into it without casting away the const. If you really want to break it nothing can stop you.

if you want to make it re-entrant.

const char *foo(int x)
{
 return (x? "not zero" : "zero");
}
pizza
  • 7,296
  • 1
  • 25
  • 22
0

I see nothing wrong with this code whatsoever.

The pointer that foo() returns is a valid pointer and you're allowed to dereference it.

Edit: By "nothing wrong" I mean that everything is syntactically ok, but I agree with other answers that this code is not good in any meaning of the word for a variety of reasons. It is merely correct according to the C standard.

Nathan Fellman
  • 122,701
  • 101
  • 260
  • 319