Почему char [] в стеке, но char * в куче?

Я очень смущен тем, что происходит. Я всегда думал, что char * и char [] являются взаимозаменяемыми, но после просмотра адресов памяти кажется, что char * выделяет пространство в куче, тогда как char [] выделяет память в стеке.

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);

Выводит следующее:

Address of stack[0]: 0x7fff8b0b85b0
Address of heap[0]: 0x400760
Address of heap_string_malloc[0]: 0x400760

Означает ли это, что char * динамически распределяется?

В дополнение к моей путанице, как получилось, что malloc выделяет тот же адрес памяти, что и выделенный char *heap? У меня нет оптимизации (просто gcc file.c).

Ответы

Ответ 1

Массивы не являются указателями. То, что делает ваша программа, по очереди,

// 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";

Причина, по которой вы видите один и тот же указатель дважды, заключается в том, что компилятор оптимизировал второй статический буфер, содержащий "hello".

Ответ 2

Когда вы делаете, например,

char *heap = "hello";

указатель heap фактически не указывает на кучу, он указывает на статические данные, загруженные вместе с остальной программой загрузчиком операционной системы. Фактически, чтобы быть правильным, это должно быть

const char *heap = "hello";

as heap указывает на постоянную и только для чтения часть памяти.


Кроме того, в то время как массивы распаковываются (и могут использоваться как) указатели, а указатели могут использоваться с синтаксисом массива, они не совпадают. Самое большое различие заключается в том, что для массива, который вы можете использовать, например, sizeof, чтобы получить размер в байтах реального массива, в то время как это невозможно для указателей.


И, в-третьих, когда вы делаете

char *heap_string_malloc = malloc(5);
heap_string_malloc = "hello";

у вас есть утечка памяти, поскольку вы сначала присваиваете что-то heap_string_malloc, а затем сразу после переназначения heap_string_malloc указываете на нечто совершенно другое.


По той причине, что вы получаете тот же адрес для heap и heap_string_malloc, потому что оба указывают на одну и ту же литеральную строку.

Ответ 3

Строковые литералы, такие как "hello", хранятся таким образом, что они хранятся в течение всего жизненного цикла программы. Они часто хранятся в отдельном сегменте данных (отличном от стека или кучи), который может быть доступен только для чтения.

Когда вы пишете

char stack[] = "hello";

вы создаете новую переменную auto ( "стек" ) типа "6-элементный массив char" (размер берется из длины строкового литерала) и содержимое строкового литерала "hello".

Когда вы пишете

char *heap = "hello";

вы создаете новую переменную auto ( "stack" ) типа "указатель на char", а адрес строкового литерала "hello" копируется на него.

Вот как он выглядит в моей системе:

       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    [email protected]
            0x7fffb00c761c   00   00   00   00    ....

      *heap       0x400b70   68   65   6c   6c    hell
                  0x400b74   6f   00   22   68    o."h

Как вы можете видеть, строковый литерал "hello" имеет свое собственное хранилище, начиная с адреса 0x400b70. Обе переменные stack ahd heap создаются как переменные auto ( "stack" ). stack содержит копию содержимого строкового литерала, а heap содержит адрес строкового литерала.

Теперь предположим, что я использую malloc для выделения памяти для строки и присваивания результата heap:

heap = malloc( sizeof *heap * strlen( "hello" + 1 ));
strcpy( heap, "hello" );

Теперь моя карта памяти выглядит следующим образом:

       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...

Теперь переменная heap содержит другой адрес, который указывает на еще один 6-байтовый блок памяти, содержащий строку "привет".

EDIT

Для байта, здесь, здесь код, который я использую для создания приведенной выше карты:

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);
  }
}

И пример того, как его использовать:

#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;
}

Ответ 4

"stack" - это статический массив символов, поэтому он будет выделен в стеке и автоматически освобожден после завершения функции, поскольку его размер известен с момента его определения. Хотя "куча" и "heap_string_malloc" объявляются как указатели на буферы char, и их необходимо распределять динамически с помощью malloc, чтобы определить размер их содержимого, поэтому они будут находиться в памяти кучи. Выполняя:

char *heap = "hello";

и

heap_string_malloc = "hello";

вы изменяете указатели на себя (со значением статического буфера), а не на контент, на который они указывают. Вы должны использовать memcpy для изменения памяти, указанной указателем "heap_string_malloc", с вашими данными:

memcpy(heap_string_malloc, "hello", 5);

Ответ 5

Это создает массив в стеке, содержащий копию статической строки "hello":

char stack[] = "hello";

Это создает указатель на стек, содержащий адрес статической строки "привет":

char *heap = "hello";

Это создает указатель на стек, содержащий адрес динамически распределенного буфера 5 байтов:

char *heap_string_malloc = malloc(5);

Но во всех трех случаях вы помещаете что-то в стек. char* не находится в куче. Это указатель (в стеке), который указывает на что-то, где-то.