Why is char[] on the stack but char * on the heap?
Solution 1
Arrays are not pointers. What your program is doing, line by line, is
// Allocate 6 bytes in the stack and store "hello" in them
char stack[] = "hello";
// Allocate pointer on the stack and point it to a static, read-only buffer
// containing "hello"
char *heap = "hello";
// Malloc 5 bytes (which isn't enough to hold "hello" due to the NUL byte)
char *heap_string_malloc = malloc(5);
// Reset heap_string_malloc to point to a static buffer; memory leak!
heap_string_malloc = "hello";
The reason you're seeing the same pointer twice is because the compiler optimized away the second static buffer containing "hello"
.
Solution 2
When you do e.g.
char *heap = "hello";
the pointer heap
actually does not point to the heap, it points to static data loaded together with the rest of program by the operating system loader. In fact, to be correct it should be
const char *heap = "hello";
as heap
is pointing to a constant and read-only piece of memory.
Also, while arrays decays to (and can be used as) pointers, and pointers can be used with array syntax, they are not the same. The biggest difference being that for an array you can use e.g. sizeof
to get the size in bytes of the actual array, while it's not possible for pointers.
And as a third thing, when you're doing
char *heap_string_malloc = malloc(5);
heap_string_malloc = "hello";
you have a memory leak, as you first assign something to heap_string_malloc
but then directly afterward reassign heap_string_malloc
to point to something completely different.
As for the reason you get the same address for both heap
and heap_string_malloc
it's because both points to the same literal string.
Solution 3
String literals such as "hello"
are stored in such a way that they are held over the lifetime of the program. They are often stored in a separate data segment (distinct from the stack or heap) which may be read-only.
When you write
char stack[] = "hello";
you are creating a new auto
("stack") variable of type "6-element array of char
" (size is taken from the length of the string literal), and the contents of the string literal "hello"
are copied to it.
When you write
char *heap = "hello";
you are creating a new auto
("stack") variable of type "pointer to char
", and the address of the string literal "hello"
is copied to it.
Here's how it looks on my system:
Item Address 00 01 02 03
---- ------- -- -- -- --
"hello" 0x400b70 68 65 6c 6c hell
0x400b74 6f 00 22 68 o."h
stack 0x7fffb00c7620 68 65 6c 6c hell
0x7fffb00c7624 6f 00 00 00 o...
heap 0x7fffb00c7618 70 0b 40 00 p.@.
0x7fffb00c761c 00 00 00 00 ....
*heap 0x400b70 68 65 6c 6c hell
0x400b74 6f 00 22 68 o."h
As you can see, the string literal "hello"
has its own storage, starting at address 0x400b70. Both the stack
ahd heap
variables are created as auto
("stack") variables. stack
contains a copy of the contents of the string literal, while heap
contains the address of the string literal.
Now, suppose I use malloc
to allocate the memory for the string and assign the result to heap
:
heap = malloc( sizeof *heap * strlen( "hello" + 1 ));
strcpy( heap, "hello" );
Now my memory map looks like the following:
Item Address 00 01 02 03
---- ------- -- -- -- --
"hello" 0x400b70 68 65 6c 6c hell
0x400b74 6f 00 22 68 o."h
stack 0x7fffb00c7620 68 65 6c 6c hell
0x7fffb00c7624 6f 00 00 00 o...
heap 0x7fffb00c7618 10 10 50 00 ..P.
0x7fffb00c761c 00 00 00 00 ....
*heap 0x501010 68 65 6c 6c hell
0x501014 6f 00 00 00 o...
The heap
variable now contains a different address, which points to yet another 6-byte chunk of memory containing the string "hello".
EDIT
For byteofthat, here's the code I use to generate the above map:
dumper.h:
#ifndef DUMPER_H
#define DUMPER_H
/**
* Dumps a memory map to the specified output stream
*
* Inputs:
*
* names - list of item names
* addrs - list of addresses to different items
* lengths - length of each item
* count - number of items being dumped
* stream - output destination
*
* Outputs: none
* Returns: none
*/
void dumper(char **names, void **addrs, size_t *lengths, size_t count, FILE *stream);
#endif
dumper.c:
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include "dumper.h"
/**
* Dumps a memory map to the specified output stream
*
* Inputs:
*
* names - list of item names
* addrs - list of addresses to different items
* lengths - length of each item
* count - number of items being dumped
* stream - output destination
*
* Outputs: none
* Returns: none
*/
void dumper(char **names, void **addrs, size_t *lengths, size_t count, FILE *stream)
{
size_t i;
int maxlen = 15;
for ( size_t j = 0; j < count; j++ )
{
if (strlen(names[j]) > maxlen && strlen(names[j]) < 50)
maxlen = strlen(names[j]);
}
fprintf(stream,"%*s%15s%5s%5s%5s%5s\n", maxlen, "Item", "Address", "00", "01",
"02", "03");
fprintf(stream,"%*s%15s%5s%5s%5s%5s\n", maxlen, "----", "-------", "--", "--",
"--", "--");
for (i = 0; i < count; i++)
{
size_t j;
char *namefield = names[i];
unsigned char *p = (unsigned char *) addrs[i];
for (j = 0; j < lengths[i]; j+=4)
{
size_t k;
fprintf(stream,"%*.*s", maxlen, maxlen, namefield);
fprintf(stream,"%15p", (void *) p);
for (k = 0; k < 4; k++)
{
fprintf(stream,"%3s%02x", " ", p[k]);
}
fprintf(stream, " ");
for ( k = 0; k < 4; k++)
{
if (isgraph(p[k]))
fprintf(stream,"%c", p[k]);
else
fprintf(stream, ".");
}
fputc('\n', stream);
namefield = " ";
p += 4;
}
fputc('\n', stream);
}
}
And an example of how to use it:
#include <stdio.h>
#include "dumper.h"
int main(void)
{
int x = 0;
double y = 3.14159;
char foo[] = "This is a test";
void *addrs[] = {&x, &y, foo, "This is a test"};
char *names[] = {"x", "y", "foo", "\"This is a test\""};
size_t lengths[] = {sizeof x, sizeof y, sizeof foo, sizeof "This is a test"};
dumper(names, addrs, lengths, 4, stdout);
return 0;
}
Solution 4
This creates an array on the stack, containing a copy of the static string "hello":
char stack[] = "hello";
This creates a pointer on the stack, containing the address of the static string "hello":
char *heap = "hello";
This creates a pointer on the stack, containing the address of a dynamically allocated buffer of 5 bytes:
char *heap_string_malloc = malloc(5);
But in all three cases, you put something on the stack. char*
is not "on the heap". It is a pointer (on the stack) which points to something, somewhere.
tgun926
Updated on September 03, 2020Comments
-
tgun926 almost 4 years
I'm very confused about what's happening. I always thought
char *
andchar []
were interchangable, but after looking at the memory addresses it seemschar *
allocates space in the heap, whereaschar []
is allocating memory on the stack.char stack[] = "hello"; char *heap = "hello"; char *heap_string_malloc = malloc(5); heap_string_malloc = "hello"; printf("Address of stack[0]: %p\n", stack); printf("Address of heap[0]: %p\n", heap); printf("Address of heap_string_malloc[0]: %p\n", heap_string_malloc);
Outputs the following:
Address of stack[0]: 0x7fff8b0b85b0 Address of heap[0]: 0x400760 Address of heap_string_malloc[0]: 0x400760
Does this mean that
char *
is dynamically allocated?Further to my confusion, how come
malloc
is allocating the same memory address as whatchar *heap
has already allocated? I'm not running any optimisation (simplygcc file.c
).