Before I answer this question and important clarification must be made about what it means for something to "work". There is a term that will come up a lot when talking about C (and other languages) called "undefined behavior". Programs which invoke undefined behavior may compile just fine and may even appear to work just fine, but it is not guaranteed to always do so. Some examples of undefined behavior include reading from an uninitialized variable (int x; printf("%d\n", x)
), writing to memory that you don't own (int arr[5]; arr[100] = 0
) or overflowing a signed integer (an integer that can represent negative values). Some of the examples that you refer to as "working" actually invoke undefined behavior (which I will cover below).
The first non-sprintf example
char *filename;
filename = "001.jpg";
This works because string literals (things enclosed in quotes) are stored inside the executable itself. When the code is compiled, the compiler essentially just goes through and replaces "001.jpg"
with its address within the executable1. In this instance, the compiler takes care of allocating the space for the string for you.
How sprintf works
The declaration of sprintf
looks like int sprintf(char *str, const char *format, ...)
. sprintf
creates the formatted string based on format
and the additional arguments you supply and writes it to the memory address2 stored in str
. The general outling of the function looks like the following (sans the actual formatting part):
int sprintf(char *str, const char *format, ...) {
// number of chars we've stored in `str`
int str_index = 0;
// For each character in the format string
for (const char *format_ptr = format; *format_ptr != '\0'; format_ptr++) {
if (*format_ptr == '%') {
// Handle formatting
} else {
// *format_ptr is just a regular character, so just store it in `str`
str[str_index] = *format_ptr;
}
str_index++;
}
return str_index;
}
The rest of your sprintf examples
Let's go through the rest of your examples one by one:
char *filename;
sprintf(filename, "001.jpg");
I said in the above section that sprintf
writes the resulting string to the memory address stored in its str
argument, so what is the memory address stored in filename
? Well since filename is not initialized, it's undefined; it's just some random address in memory. This doesn't work because you're writing the contents of "001.jpg"
to some arbitrary location in memory which is undefined behavior. This is the same reason that char *str; *str = 'c';
is undefined behavior; you're writing 'c'
to an arbitrary location in memory;
char *filename = NULL;
sprintf(filename, "001.jpg");
This doesn't work for a reason similar to the last example, but rather than trying to write the contents of "001.jpg"
to some arbitrary location in memory, it tries to write it beginning at the NULL
address. Writing to/reading from the NULL
pointer is undefined behavior which (may) crash your program.
char *filename = malloc(sizeof(char)*8);
sprintf(filename, "001.jpg");
This works because filename
contains the address of a block of memory that you've allocated, and it doesn't invoke undefined behavior because you've allocated enough space to store "001.jpg" plus the null byte.
char filename[8];
sprintf(filename, "001.jpg");
Again this works because filename
refers to a block of memory that you've allocated (the only difference being that it is on the stack rather than the heap).
Your malloc examples
char *name; // Not working, IDE requested to assign NULL
name[0] = 1;
This invokes undefined behavior as mentioned above. name
contains some undefined value and name[0] = 1
tries to interpret that arbitrary value as a memory address and writes to it.
char *name = NULL;
name[0] = 1; // Not working, because no memory assigned to store "1"?
Spot on. NULL
cannot be written to or read from.
char *name = malloc(2);
name[0] = 1; // why this works?
This is correct because malloc
allocated two bytes on the heap and returned the address of this allocated block. It's perfectly valid to read from/write to this block.
char *name = malloc(0);
name[0] = 1; // why this still works?
This is actually undefined behavior and only works by happenstance (and certainly is not guaranteed to always work). malloc(0)
may return one of two things (depending on the implementation):
- A NULL pointer
- Some other pointer that is allowed to be passed to
free
In this case, it is doing the latter. malloc
is returning to you an address but one that does not actually refer to a block you're supposed to write to.
char *name = malloc(2);
name[0] = "A"; // why this doesn't work?
This is again (probably) undefined behavior, but perhaps not the reason you'd expect. As mentioned in my first answer, the compiler will replace "A" with a memory address that points to the first character of that string. On 64-bit computers, pointers are (usually) 8 bytes (can store values from 0-18446744073709551615) while char
s are one byte (can store values from either -127-128 or 0-255 depending on if it is signed or not). name[0] = "A"
will convert the memory address into an 8-byte integer (that is probably quite large) and attempt to store that in a one-byte char
. This will overflow the char, which if it is signed on your platform, results in undefined behavior. Even if the behavior is defined, it certainly not copy the string as you would presumably expect. To copy the string, you would do the following: strcpy(name, "A")
;
1The compiler will often put these string literals in read-only areas of memory which is why attempting to modify a string literal (char *str = "hello"; str[0] = 'm'
) is undefined behavior.
2It's possible that the resulting string will be too long for the amount of memory you've allocated for str
which is why it is generally recommended that you use snprintf
instead of sprintf
which also takes the size of the buffer, ensuring that it won't accidentally write to out-of-bounds memory.