Почему 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*
не находится в куче. Это указатель (в стеке), который указывает на что-то, где-то.