Почему адреса памяти строковых литералов отличаются от других?

Я заметил, что строковые литералы имеют очень разные адреса в памяти, чем другие константы и переменные (ОС Linux): у них много ведущих нулей (не напечатано).

Пример:

const char *h = "Hi";
int i = 1;
printf ("%p\n", (void *) h);
printf ("%p\n", (void *) &i);

Вывод:

0x400634
0x7fffc1ef1a4c

Я знаю, что они хранятся в части .rodata исполняемого файла. Есть ли особый способ, которым ОС обрабатывает его впоследствии, поэтому литералы попадают в специальную область памяти (с ведущими нулями)? Есть ли преимущества в этом месте памяти или есть что-то особенное?

Ответ 1

Здесь как память процесса выложена в Linux (от http://www.thegeekstuff.com/2012/03/linux-processes-memory-layout/):

Платформа памяти Linux

Раздел .rodata​​strong > является защищенным от записи подразделением блока Initialized Global Data. (Раздел, в котором ELF исполняемые файлы обозначают .data​​strong > , является его записываемым экземпляром для записываемых глобальных переменных, инициализированных ненулевыми значениями. к нулям перейдите в блок .bss. В качестве глобальных переменных я имею в виду глобальные переменные и все переменные static, независимо от места размещения.)

На картинке должны быть указаны числовые значения ваших адресов.

Если вы хотите продолжить исследование, то в Linux вы можете проверить /proc/$pid/maps виртуальные файлы, которые описывают структуру памяти запущенных процессов. Вы не получите зарезервированные (начиная с точки) имена разделов ELF, но вы можете догадаться, из какой секции ELF возникает блок памяти, просматривая флаги защиты памяти. Например, запуск

$ cat /proc/self/maps #cat memory map

дает мне

00400000-0040b000 r-xp 00000000 fc:00 395465                             /bin/cat
0060a000-0060b000 r--p 0000a000 fc:00 395465                             /bin/cat
0060b000-0060d000 rw-p 0000b000 fc:00 395465                             /bin/cat
006e3000-00704000 rw-p 00000000 00:00 0                                  [heap]
3000000000-3000023000 r-xp 00000000 fc:00 3026487                        /lib/x86_64-linux-gnu/ld-2.19.so
3000222000-3000223000 r--p 00022000 fc:00 3026487                        /lib/x86_64-linux-gnu/ld-2.19.so
3000223000-3000224000 rw-p 00023000 fc:00 3026487                        /lib/x86_64-linux-gnu/ld-2.19.so
3000224000-3000225000 rw-p 00000000 00:00 0
3000400000-30005ba000 r-xp 00000000 fc:00 3026488                        /lib/x86_64-linux-gnu/libc-2.19.so
30005ba000-30007ba000 ---p 001ba000 fc:00 3026488                        /lib/x86_64-linux-gnu/libc-2.19.so
30007ba000-30007be000 r--p 001ba000 fc:00 3026488                        /lib/x86_64-linux-gnu/libc-2.19.so
30007be000-30007c0000 rw-p 001be000 fc:00 3026488                        /lib/x86_64-linux-gnu/libc-2.19.so
30007c0000-30007c5000 rw-p 00000000 00:00 0
7f49eda93000-7f49edd79000 r--p 00000000 fc:00 2104890                    /usr/lib/locale/locale-archive
7f49edd79000-7f49edd7c000 rw-p 00000000 00:00 0
7f49edda7000-7f49edda9000 rw-p 00000000 00:00 0
7ffdae393000-7ffdae3b5000 rw-p 00000000 00:00 0                          [stack]
7ffdae3e6000-7ffdae3e8000 r--p 00000000 00:00 0                          [vvar]
7ffdae3e8000-7ffdae3ea000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

Первый блок r-xp определенно пришел из .text (исполняемый код), первый блок r--p от .rodata​​strong > и следующие rw-- блоки от .bss и .data​​strong > . (Между кучей и блоком стека находятся блоки, загруженные из динамически связанных библиотек динамическим компоновщиком.)


Примечание.. Чтобы соответствовать стандарту, вы должны использовать int* для "%p" до (void*), иначе поведение undefined.

Ответ 2

Это потому, что строковые литералы имеют статическую продолжительность хранения. То есть, они будут жить в течение всей программы. Такие переменные могут храниться в специальной ячейке памяти, которая не находится ни в так называемой куче, ни в стеке. Отсюда разница в адресах.

Ответ 3

Помните, что где указатель отличается от того, на который указывает указатель. Сравнение более реалистичных (яблок-яблок) было бы

printf ("%p\n", (void *) &h);
printf ("%p\n", (void *) &i);

Я подозреваю, что вы найдете, что h и p имеют похожие адреса. Или другое более реалистичное сравнение было бы

static int si = 123;
int *ip = &si;
printf ("%p\n", (void *) h);
printf ("%p\n", (void *) ip);

Я подозреваю, что вы обнаружите, что h и ip указывают на аналогичную область памяти.

Ответ 4

printf ("%p\n", h); // h is the address of "Hi", which is in the rodata or other segments of the application.
printf ("%p\n", &i); // I think "i" is not a global variable, so &i is in the stack of main. The stack address is by convention in the top area of the memory space of the process.

Ответ 5

Считайте, что литералы являются переменными только для чтения, а также существует понятие литерального пула. То, что представляет собой литеральный пул, представляет собой набор уникальных литературных программ, где дублирующие константы отбрасываются, поскольку ссылки объединяются в один.

Существует один литеральный пул для каждого источника, и в зависимости от сложности программы link/bind, литеральные пулы могут быть размещены рядом друг с другом для создания одной .rodata.

Также нет гарантии, что литеральный пул защищен только для чтения. Язык, хотя дизайн компилятора трактует его так.

Рассмотрим фрагмент кода. Я мог бы

const char * cp = "hello world";
const char * cp1 = "hello world";

Хороший компилятор распознает, что в этом исходном коде литералы только для чтения cp, cp1, указывают на одинаковые строки и сделают cp1 указывать на cp-литерал, отбрасывая второй.

Еще один момент. Литеральный пул может быть кратным 256 байтам или различному значению. Если данные пула меньше 256 байт, провисание будет дополняться шестнадцатеричными нулями.

Различные компиляторы, следуя общим стандартам разработки, разрешая модуль, скомпилированный с C, быть связанными с модулем, скомпилированным с языком ассемблера или другим языком. Два литеральных пула помещаются последовательно в .rodata.