Why is char[] on the stack but char * on the heap?

21,178

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.

Share:
21,178
tgun926
Author by

tgun926

Updated on September 03, 2020

Comments

  • tgun926
    tgun926 almost 4 years

    I'm very confused about what's happening. I always thought char * and char [] were interchangable, but after looking at the memory addresses it seems char * allocates space in the heap, whereas char [] 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 what char *heap has already allocated? I'm not running any optimisation (simply gcc file.c).