Пожалуйста, я хочу знать реальность того, как переменные, массивы и строки хранятся в памяти на языке C. Пожалуйста, исправьте мое понимание памяти в приведенном ниже примере:
В C, когда мы объявляем строку, мы выполняем следующие действия:
char string[]="string";
Что происходит в памяти? Каждый символ хранится в памяти и каждый случай памяти имеет свой адрес?
Например, адрес
1600 char[0]='S'
1602 char[2]='t'
и так далее. Это правда? Если нет, пожалуйста, дайте мне правильную схему того, что на самом деле происходит.
Мой второй вопрос касается C++: В C++ они изобрели новый тип данных, который является string
. Например:
string variable("This is a string");
Как этот текст ("Это строка") хранится в памяти?
Посмотрите строки в глубину:
[...] Точная реализация макета памяти для класса строк не определена стандартом C++. Эта архитектура призвана быть достаточно гибкой, чтобы допускать различные реализации поставщиков компилятора, но гарантировать предсказуемое поведение для пользователей. [...]
[...] В C++ отдельные строковые объекты могут занимать или не занимать уникальные физические области памяти, но если подсчет ссылок используется для предотвращения дублирования копий данных, отдельные объекты должны выглядеть и действовать так, как если бы они выполнялись исключительно собственные уникальные регионы хранения. [...]
Вы можете узнать, как string
реализована вашим компилятором следующим образом. Для меня (тест под VS2010), это будет
string variable("This is a string");
printf("%p\n", &variable[0]); // 006751C0
printf("%p\n", &variable[1]); // 006751C1
printf("%p\n", &variable[2]); // 006751C2
printf("%p\n", &variable[3]); // 006751C3
printf("%p\n", &variable[4]); // 006751C4
printf("%p\n", &variable[5]); // 006751C5
... ...
Строка хранится как массив с нулевым завершающим символом (каждый символ в ASCII-коде имеет ширину 8 бит и таким образом он занимает ровно один байт), так что им нужен еще один байт, чтобы сохранить символ "\ 0" ("привет") требуется массив из трех байтов). Если вы используете std :: string в c++, память, используемая для хранения данных, такая же, но вокруг нее есть оболочка, которая позволяет автоматически управлять памятью, так что когда вы делаете:
string s("hello");
string t("world");
s+=t;
s расширяется или даже перемещается в другое место в памяти, если это необходимо, чтобы найти более длинный последовательный массив байтов. Точно так же, когда вы звоните
s.c_str();
вы получаете указатель на нуль-завершенный массив символов, содержащийся в экземпляре s std :: string, этот указатель является лишь временным указателем, так что это
char* text=s.c_str();
s="very long string that probably results in a complete reallocation of the array";
printf("%s",text);
вероятно, приведет к неопределенному поведению (ошибка сегментации)
std::basic_string
и 2. Null-terminated strings
.
Каждый раз, когда вы создаете (компилируете и связываете) свой проект, создается исполняемый образ, разделенный на три раздела:
Code-раздел
Данных Раздел
стек
Сценарий №1 - локальный массив (объявленный внутри функции):
int func()
{
char array[]="string";
...
}
После загрузки и запуска программы каждый раз, когда вызывается функция, 7 байтов, расположенных где-то в стеке, инициализируются следующими значениями: 's', 't', 'r', 'i', 'n', ' g ', 0. Адрес в стеке, где расположены эти байты, является значением регистра SP при вызове функции. Следовательно, каждый раз, когда вызывается функция, эти 7 байтов могут находиться в другом адресе в памяти.
Сценарий №2 - глобальный массив (объявленный вне функции):
char array[]="string";
Во время компиляции (перед загрузкой и запуском программы) 7 байтов в разделе данных задаются со следующими значениями: 's', 't', 'r', 'i', 'n', 'g', 0. Эти байты "жестко закодированы" в исполняемом изображении, и как только они загружаются в память (т.е. Всякий раз, когда вы запускаете программу), они находятся в одном и том же адресе во время выполнения программы.
Сценарий № 3 - указатель на локальный массив (объявленный внутри функции):
int func()
{
char* array="string";
...
}
То же, что и сценарий № 2, за исключением того факта, что эти 7 байтов находятся в разделе кода (который является разделом только для чтения), а не в разделе данных (который является секцией чтения/записи). Кроме того, указатель (array
) выделяется в стеке и инициализируется (устанавливается так, чтобы указывать на адрес "string"
в разделе кода) каждый раз, когда вызывается функция. Размер этого указателя обычно составляет 4 байта при использовании 32-разрядной ОЗУ или 8 байтов при использовании 64-разрядной ОЗУ.
Сценарий №4 - указатель на глобальный массив (объявленный вне функции):
char* array="string";
То же, что и сценарий № 3, за исключением того факта, что указатель (array
) находится в секции данных вместо стека и инициализируется один раз (во время компиляции), а не каждый раз, когда вызывается функция (во время выполнения).
они сохраняются в виде байтов: char: 1 byte
short: 2 bytes
int: 4 bytes
long: 4 bytes
float: 4 bytes
double: 8 bytes
short
- это два байта? что int
это 4? что long
всегда 4? Потому что это не так.
long
составляет 32 бита на 32- и 64-разрядных платформах, в то время как при использовании GCC long
составляет 32 бита на 32-разрядных платформах и 64-разрядных на 64-разрядных платформах. Кроме того, хотя sizeof(char)
определен так, чтобы возвращать 1
, char
может фактически быть чем-то иным, чем байтом (8 бит).
"string"
по-прежнему представляет собой массив из 7 элементов. КлассString
, OTOH, обладает гораздо большей функциональностью.