2

I can't seem to understand what exactly is going on here

#include <stdio.h>

const char* mes(int a)
{
    static char mess[100];
    sprintf(mess, "%d", a);
    return mess;
}
const int* hes(int a)
{
    static int arr[100];
    arr[0] = a;
    return arr;
}

int main()
{
    printf("%s %s\n", mes(55), mes(25)); //55 55
    printf("%s\n", mes(55)); //55
    printf("%s\n", mes(25)); //25
    printf("%d %d\n", hes(55)[0], hes(25)[0]); //55 25
}

In the first printf the second function seems to be ignored and the output of the earlier input gets printed again.
At first I assumed it was a static variable issue, so I tried printing them separately and then they seem to work fine.
Then I assumed it was a printf issue so I tried to simulate the same behavior with an integer array, and it worked fine there too.
I've run this program a couple of times with various inputs, ruling out the possibility of UB.
So, what exactly am I missing here?

EDIT: I encountered this issue somewhere else and couldn't understand what was happening. So I reproduced the issue in a short sample code. But my question stands, (as many have mentioned) are all the parameters evaluated before printing? If so there should be an overwrite in both cases (int and char array) regardless of evaluation order.

Raheeb Hassan
  • 109
  • 1
  • 7
  • 1
    Possible duplicate of [Parameter evaluation order before a function calling in C](https://stackoverflow.com/questions/376278/parameter-evaluation-order-before-a-function-calling-in-c) – Achal Dec 19 '18 at 16:16
  • 2
    You can never rule out UB by observing the actual behavior of a program. UB does not necessarily mean "it crashes" or "it behaves inconsistently" or "it does something unexpected", though of course your program *does* do something you don't expect. – John Bollinger Dec 19 '18 at 16:16
  • 1
    There is no UB here, and the suggested duplicate is not a duplicate. – Antti Haapala -- Слава Україні Dec 19 '18 at 16:27

5 Answers5

5

The order of evaluation of function parameters is unspecified, meaning that you can theoretically see 25 25 instead. That's first thing. Second, when printf is called, both functions have been already evaluated, and the same pointer is passed as first and the second string (because it is a static location), which is the result of the last evaluation (55 in your case). So identical text is printed.

This is pretty much equivalent to the following:

char* a = mes(25);
char* b = mes(55);

// Note, the above can swap depending on the order of evaluation

printf("%s %s\n", a, b);

But here a equals b as both are pointing to a static array.

As for the second example this is not happening as it will be equivalent to the following (up to the order of evaluation):

int *ap = hes(55);
int a = ap[0];

int *bp = hes(25);
int b = bp[0];

printf("%d %d\n", a, b);

Note, that here the pointed values are passed and not the pointer itself. So even though ap equals bp, a is not the same as b.

Eugene Sh.
  • 17,802
  • 8
  • 40
  • 61
  • But shouldn't it be the same for the integer array? – Raheeb Hassan Dec 19 '18 at 16:22
  • So if I understand correctly, when pointers are involved parameters are all evaluated before printing. While when passing integers (by value) the printing is done in-place? – Raheeb Hassan Dec 19 '18 at 16:47
  • 1
    No, no. Parameters are *always* passed by value. Whenever they are pointer or not. It is just that in the first case two *identical* values have been passed to the function, but in second case two different ones were passed. Do you see why the code above is equivalent to yours? – Eugene Sh. Dec 19 '18 at 16:49
  • Thanks, I think I got it. – Raheeb Hassan Dec 19 '18 at 17:03
1

The why code is wrong is well explained by others.

An alternative to static char mess[100] is to use a compound literal as an argument. Then the return value from mes(), hes() is valid until the end of the block of code - well after the printf().

#include <stdio.h>

const char* mes_helper(char mess[100], int a) {
  sprintf(mess, "%d", a);
  return mess;
}
const int* hes_helper(int arr[100], int a) {
  arr[0] = a;
  return arr;
}

// compound literal        v-------------v
#define mes(a) mes_helper( (char [100]){0}, (a))
#define hes(a) hes_helper( (int  [100]){0}, (a))

// No changes to `main() code
int main(void) {
  printf("%s %s\n", mes(55), mes(25)); //55 55
  printf("%s\n", mes(55)); //55
  printf("%s\n", mes(25)); //25
  printf("%d %d\n", hes(55)[0], hes(25)[0]); //55 25
}

Output

55 25
55
25
55 25
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
0

There is only one variable mess. When you call:

printf("%s %s\n", mes(55), mes(25));

You are filling in that one variable two different times, once with "25" and once with "55" (overwriting the "25"). As a result, when printf goes to format with %s %s, it finds the same string twice, "55" and again "55" because the "25" has already been overwritten.

You need to be aware that all parameters to a function are evaluated before the function is called. The order of parameter evaluation is not defined, but is often right-to-left. Breaking down that printf into little steps:

  1. Evaluate the mes(25); now static char mess is now "25".
  2. Evaluate the mes(55), now static char mess is overwritten to "55".
  3. Evaluate the parameter "%s %s\n" (there isn't much to evaluate here. Its just a string)
  4. Call printf with parameters: "%s %s\n", and "55" and "55"
abelenky
  • 63,815
  • 23
  • 109
  • 159
  • If all parameters are evaluated before function call regardless of evaluation order, shouldn't there be an overwrite in the case of the integer array as well? – Raheeb Hassan Dec 19 '18 at 16:26
0

When you call:

printf("%s %s\n", mes(55), mes(25));

mes(55) and mes(25) get evaluated before their result is populated into the string. And since you're pointing to the same static memory, when it's time to populate the string, you get the same value.

Kon
  • 4,023
  • 4
  • 24
  • 38
0

Returning the address of a static variable is almost always a bad pattern as you'e found. There's only one such static allocation for the whole program, so for example calling mes twice causes the second call to overwrite the results of the first.

A more reasonable pattern is having the caller furnish a buffer. This is also needed for thread safety:

#include <stdio.h>

const char* mes(char *buf, int a)
{
    sprintf(buf, "%d", a);
    return buf;
}

const int* hes(int *arr, char *buf, int a)
{
    arr[0] = a;
    return arr;
}

int main()
{
    char buf1[100], buf2[100];
    int arr[100];
    printf("%s %s\n", mes(buf1, 55), mes(buf2, 25)); //55 55
    printf("%s\n", mes(buf1, 55)); //55
    printf("%s\n", mes(buf1, 25)); //25
    printf("%d %d\n", hes(arr, 55)[0], hes(arr, 25)[0]); //55 25
}
Gene
  • 46,253
  • 4
  • 58
  • 96