2

Could you please explain me memory allocation in C for strings and integer array after using sprintf function?

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

int main() {
    char str[10];
    long long int i = 0, n = 7564368987643389, l;
    sprintf(str, "%lld", n);  // made it to a string 
    printf("%c%c", str[11], str[12]);
}

In the above code, string size is 10 inclusive of null character. How come we access 11 and 12 elements in it? The program prints 43

chqrlie
  • 131,814
  • 10
  • 121
  • 189

5 Answers5

6

Here

sprintf(str,"%lld",n);

n i.e 7564368987643389 converted into character buffer & stored into str. It looks like

str[0]     str[2]   str[4]    str[6]     str[8]   str[10]   str[12] ..
--------------------------------------------------------------------------------------
| 7  | 5  | 6  | 4  | 3  | 6  | 8  | 9  | 8  | 7  | 6  | 4  | 3  | 3  | 8  | 9  | \0 |
--------------------------------------------------------------------------------------
str   str[1]    str[3]    str[5]    str[7]    str[9]    str[11] ..

As you can see str[11] is 4 and str[12] is 3. Hence the printf() statement below prints:

printf("%c %c",str[11],str[12]);

4 3

But since you have declared str of 10 characters and in sprintf() you are trying to store more than 10 characters, it causes undefined behavior.

Achal
  • 11,821
  • 2
  • 15
  • 37
5

You’ve written past the end of the array into memory you don’t own - the behavior on doing so is undefined, and any outcome is possible.

C does not require bounds checking on array writes or accesses. You won’t get an ArrayIndexOutOfBounds-type exception if you index past the end of the array.

In this case you didn’t overwrite anything important, so the program ran as you would expect, but it doesn’t have to. You could have corrupted data in an adjacent object, or you could have gotten a runtime error from the OS.

You are responsible for knowing how big the target buffer is and not reading or writing past the end of it. The language will not protect you here.

John Bode
  • 119,563
  • 19
  • 122
  • 198
  • But using `snprintf()` instead of `sprintf()` would prevent writing beyond the end — though care is required to detect whether truncation occurred. – Jonathan Leffler Jul 29 '19 at 06:20
  • 1
    @JonathanLeffler: Right - the thing is that you have to know that `snprintf` exists, how to use it properly, how to know when it won't terminate the string for you, etc. This is what I mean by "the language will not protect you" - you have to protect yourself by knowing which tools to use and how to use them. – John Bode Jul 29 '19 at 18:52
3

Could you please explain me memory allocation in C for strings and integer array?

Most of the time in C, memory allocation is your responsibility. In particular, when you call functions such as strcpy, sprintf and fread, that write potentially arbitrarily many characters to a buffer you supply, it is your responsibility to ensure, somehow, beforehand, that the buffer is big enough.

Some functions, such as fread, let you say how big your buffer is, so that they can be sure not to overflow it. Others, such as strcpy, sprintf, and scanf with directives like %s, do not. You must be especially careful with these functions.

When you write something like

char str[10];
sprintf(str, "%lld", 7564368987643389);

where you supply a buffer that is not big enough for the result, two questions tend to arise:

  1. Why didn't it work?

  2. Why did it work?

If it didn't work, the reason why should be obvious: the destination buffer simply wasn't big enough. And if despite that problem, it did seem to work, the reason is because C doesn't typically enforce (doesn't explicitly guard against) buffer overflow.

Suppose I buy some land -- a 1600 square foot plot -- in an undeveloped neighborhood. Suppose my title deed says:

The property line runs south for 40 feet from an iron stake, then 40 feet west, then 40 feet north, then 40 feet east.

So I've got 40 x 40 foot plot of land, but the only feature on the ground that positively identifies where my property is, is an iron stake at one corner. There isn't a crisp black line painted on the soil, or anything, precisely delineating the property lines.

Suppose I hire an architect and a builder, and we build a house on my new plot of land, and we screw up our measurements, and we build the house 10 feet into a neighboring parcel of land (that I don't own). What happens?

What does not happen is that the instant we dig that first footing or pour that first concrete or erect that first wall that crosses over the property line, a giant error message appears in the sky saying "PROPERTY LINE EXCEEDED".

No, this kind of error is not guaranteed to get detected right away. The problem might not be noticed (by a building inspector, or by the owner of the adjacent property) until tomorrow, or next week, or next year; and under some circumstances it might never get noticed at all.

And the situation is just about exactly the same with C memory allocation. If you write more to an array than it's allocated to hold, the problem might not reveal itself for a while, or it might not reveal itself at all: the program might seem to work perfectly, despite this reasonably dire error it contains.

For this reason, not only do you have to be careful with memory allocation in C, there are some good habits to get into. Not only is it important to declare you arrays (or malloc your buffers) big enough, but you also want to make sure that, whenever possible, the size is checked. For example:

  • When you call functions like fread, that accept a destination buffer and the size of that buffer, make sure the size you pass is accurate.

  • Instead of calling functions like sprintf that accept a destination buffer but with no way to specify its size, prefer alternative functions like snprintf that do allow the size to be specified and that therefore can guard against overflow.

  • If there's a function that doesn't allow the buffer size to be specified and for which there's no better alternative, maybe just don't use that function at all. Examples are strcpy and scanf with the %s and %[ directives.

  • When you write your own functions that accept pointers to buffers and that write characters or other data into those buffers, make sure you provide an argument by which the caller can explicitly supply the buffer size, and make sure your function honors this limit.

Steve Summit
  • 45,437
  • 7
  • 70
  • 103
3

The program has undefined behavior because:

  • sprintf stores the characters 7564368987643389 plus a null terminator (a total of 17 bytes) in the destination array str defined with a length of only 10 bytes. sprintf does not receive the length of the array, hence writes the output to memory beyond the end of the array if it is too short. You should always use snprintf(str, sizeof str, "%lld", n); to avoid such undefined behavior.

  • printf("%c%c", str[11], str[12]); reads 2 bytes beyond the end of the str array, namely the 12th and the 13th bytes. This has undefined behavior, but if printf did successfully store the 17 bytes into the memory starting at the address of str, reading these bytes may yield the values '4' and '3', and produce an output of 43, which may or may not be visible as you did not end the program's output with a newline.

Writing and reading beyond the end of an array has undefined behavior, it may cause the program to crash or may seem to function as expected, but undesirable side effects can occur and go unnoticed for a while. On your system it seems nothing bad happened, but on some other system, it may cause tremendous damage... imagine if the program were running as part of a nuclear powerplant regulation system, you would not want to test the system's resilience this way.

chqrlie
  • 131,814
  • 10
  • 121
  • 189
0

I suggest creating some function you need. Like this.

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

char* LL_Int_To_Str(long long int A){

    int Lenz=0,i;
    int NegFlag=0;
    if(A<0){
        A=~A+1;
        NegFlag=1;
    }
    long long int B=A;
    do{
        B/=10;
        Lenz++;
    }while(B);
    char *Result=(char*)malloc(sizeof(char)*(Lenz+1+NegFlag));
    Result[Lenz+NegFlag]='\0';
    for(i=Lenz-1;i>-1-NegFlag;i--){
        Result[i+NegFlag]=(A%10)+48;
        A/=10;
    }
    if(NegFlag){
        Result[0]='-';
    }
    return Result;


}
int main(){


    int i;
    long long int n=7564368987643389;
    //long long int n=-7564368987643389;
    char* StrX=LL_Int_To_Str(n);
    /*char* Loop;//Debug
    for(Loop=StrX;*Loop!='\0';Loop++){
        printf("%c\n",*Loop);
    }*/

    printf("%s\n",StrX);

    free(StrX);


    return 0;
}

Then you can make sure nothing mistake.

Sometimes you think you can use some function save your time.

But most of time ,it's waste.


Another suggest

#include <limits.h>
printf("%lld\n",LLONG_MAX);

You can find max of long long int is 9223372036854775807

So max size of string requires is 19+1+1 (1 for '\0' 1 for '-')

Just use char str[21]; solve all problem

J CHEN
  • 494
  • 3
  • 9