0

my code as following:

char* int2str(int val);

void main(){
 char *s = int2str(1001);
 printf("----s=%s\n",s);
}

char* int2str(int val){
  char turnStr[10];
  sprintf(turnStr, "%d", val);
  //printf("turnStr=%s\n",turnStr);
  return turnStr;
}

The above code print out empty string, but when I uncommented the line:printf("turnStr=%s\n",turnStr) It was able to print out the right string. I knew the stack space can not return when the function was over, but I'm confused about when I added printf("turnStr=%s\n",turnStr), it could print out the string.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
Jack
  • 5,540
  • 13
  • 65
  • 113
  • Your wrapper around `sprintf` actually turns out to be a huge liability here. Just use `sprintf` directly if you must, or `printf` instead. – tadman Jun 15 '20 at 23:48
  • 1
    Amazing that people don't read the question well. Your question was "why **did** it work?", and everone else answered "why didn't it work?". – David G. Jun 16 '20 at 00:51
  • @DavidG. that depends on your definition of "work" – M.M Jun 16 '20 at 01:02
  • @M.M In this case, output the line reading "----s=1001" is "work". He asked why this came out instead of, in one case of mine, with C escapes added, "-----s=\x98\x06@" – David G. Jun 16 '20 at 01:06
  • This (why did it work) is one of the most common FAQ below the C tag, yet 5 people post answers... some answers not even answering the question. For those unaware, we have a collection of canonical dupe targets you can/should be using in these cases. Check the [C tag wiki](https://stackoverflow.com/tags/c/info), scroll down to FAQ. – Lundin Jun 16 '20 at 08:04
  • @Lundin Personally, I don't think the question you've referenced is a good match. That question isn't actually likely to give any other result (except for a recent compiler that NULLs the return value). There isn't anything overwriting data in that case. It might be closer if the data had been output repeatedly without resetting it. The fact that it is C++ doesn't help. This case was asking about why it worked sometimes and not others. – David G. Jun 17 '20 at 00:52
  • @DavidG. It's a perfect match for people trying to "prove" how their undefined behavior code related to scope "works". Better with an analogy than to clobber them with some "what is undefined behavior" link. At any rate, this has been asked thousand times before. Feel free to dig up other duplicates, there should be plenty. – Lundin Jun 17 '20 at 06:59

5 Answers5

2

Wonderful!

The basic problem is that you returned the address of something on the stack, and it was changed by something else. I tried a recent gcc and it didn't even return the stack pointer, so I tried gcc 4.4.5 and reproduced your behavior.

I tried changing main to:

void main(){
 char *s = int2str(1001);
 printf("----s=%s\n",s);
 s = int2str(1002);
 printf("----s=%s\n",s);
}

and the second printf() output 1002.

I think what is happening is that printf has some local variables that were placed in the same location as your array and that aren't used if you have previously invoked printf().

Note that it didn't print as empty but as garbage. That garbage might start with a NUL, or not.

In any case, everyone else is right that you shouldn't do this. There are a number of solutions, including:

  1. dynamic memory allocation (which means you need to free it)
  2. passing in a buffer (adds parameters ... you should pass in the length)
  3. using a static buffer (problematic for threading or multiple uses)
  4. returning a structure by value containing the text (can copy more than it should, which could cause performance issues, and you have to save the structure in the caller)
  5. eliminating this function altogether (which might not be a good solution depending on what you are doing)
Community
  • 1
  • 1
David G.
  • 595
  • 1
  • 4
  • 11
1

You're returning a reference to local variable char turnStr[10]. When function exits, the memory used by that reference is cleaned up. So in main() you're left with a dangling pointer: char *s points to memory that is no longer valid.

Kon
  • 4,023
  • 4
  • 24
  • 38
  • 1
    No. You can return automatic storage object. You should (because you can of course) not return the **reference** to this object. `int foo(void){int a=5; return a;}` is fine. `int *foo(void){int a=5; return &a;}` is not. – 0___________ Jun 15 '20 at 23:56
  • Doesn't answer the question "It was able to print out the right string. I knew the stack space can not return when the function was over, but I'm confused about when I added printf("turnStr=%s\n",turnStr), it could print out the string." – Lundin Jun 16 '20 at 08:07
1

The char array is stored on the stack frame of the int2str function. That means that while the function is still running, the memory on the stack is stable and usable. That's why you can print out the string. However, once you return from the function, there's no guarantee that the memory will be maintained and it can be cleared out or reused like you witnessed.

Aplet123
  • 33,825
  • 1
  • 29
  • 55
  • it can be stored, but it does not have to as C standard does not know anything about the stack. It does not matter where automatic storage objects are stored only their scope – 0___________ Jun 16 '20 at 00:00
0

Returning reference (pointer) to the local object is an Undefined Behaviour. Many modern compilers emit the warning and assign this pointer with the NULL - for example gcc. Another problem you have with this code is another UB. Your char array is not long enough to accommodate the string

how to sort it out (an example):

char* int2str(int val);

void main(){
 char *s = int2str(1001);
 printf("----s=%s\n",s);
}

char* int2str(int val){
  static char turnStr[20];
  sprintf(turnStr, "%d", val);
  //printf("turnStr=%s\n",turnStr);
  return turnStr;
}

https://godbolt.org/z/F3cx3E

0___________
  • 60,014
  • 4
  • 34
  • 74
0

For starters according to the C Standard the function main without parameters shall be declared like

int main( void )

that is its return type shall be int.

Your program has undefined behavior because the returned pointer from the function int2str points to a local array with the automatic storage duration that will not be alive after exiting the function. So the pointer will have an invalid value. The memory occupied by the local array can be overwritten by a call of any other function (for example by a call of printf within main).

So you have to allocate dynamically memory for the destination string. To use a local array with static storage duration within the function is not a good idea because calling the function several times will lead that previous result strings will be overwritten.

Pay attention to that for example the value of INT_MIN (the user of the function can pass any valid integer value) can be equal to -2147483648 that requires a character array of 12 elements to store the string that represents such a number.

To calculate the required size of the string you can use a call of the C function snprintf with the second argument equal to 0.

Here is a demonstrative program.

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

char * int2str( int x )
{
    int n = snprintf( NULL, 0, "%d", x );

    char *s = malloc( n + 1 );

    if ( s )
    {
        snprintf( s, n + 1, "%d", x );
    }

    return s;
}

int main(void) 
{
    char *s = int2str( 1001 );

    if ( s ) puts( s );

    free( s );

    return 0;
}

Its output is

1001
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • That is some clever way to find the bytes you require to store an integer number, but it is not portable. See the notes of ``snprintf()``, "The glibc implementation of the functions snprintf() and vsnprintf() conforms to the C99 standard, that is, behaves as described above, since glibc version 2.1. Until glibc 2.0.6, they would return -1 when the output was truncated." – m0hithreddy Jun 16 '20 at 05:24
  • "...according to the C Standard the function main without parameters shall be declared like..." That's according to the _hosted environment_ sub-chapter of execution environments. Implementation-defined forms of main() are allowed, for example all freestanding systems use implementation-defined forms of main(), where `void main (void)` is most common. See [this](https://stackoverflow.com/a/31263079/584518). – Lundin Jun 16 '20 at 08:13