2016-11-18 3 views
62

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

Пример:

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

Выход:

0x400634 
0x7fffc1ef1a4c 

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

+0

http://stackoverflow.com/questions/4560720/why-does-the-stack-address-grow-towards-decreasing-memory-addresses –

+5

Все зависит от операционной системы, в которой он загружает код и где он выделяет стек. –

+8

Очевидно, что данные, определенные реализацией, но данные RO (ваш литерал) часто загружаются на отдельные страницы, помеченные для запуска режима исключения по принципу защиты от записи. Значение: запись в него вызывает структурированное исключение. – WhozCraig

ответ

73

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

Linux process memory layout

Раздел .rodata является защищенной от записи подраздел блока Initialized Global Data. (раздел, который ELF Исполняемых назначает .data является аналогом для записи перезаписываемых глобал инициализированных ненулевых значений. Записываемые Глобал инициализируются нулям перейти к блоку .bss. По глобал здесь я имею в виду глобальных переменные и все статической переменные независимо от места размещения.)

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

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

$ cat /proc/self/maps #cat's 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, а также следующие rw-- блоки от .bss и .data. (Между кучей и блоком стека находятся блоки, загруженные из динамически связанных библиотек динамическим компоновщиком.)


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

+0

Спасибо, это полезно! Но если у меня есть несколько процессов, это все равно произойдет. Таким образом, он не выкладывает их один за другим, а принимает все «Инициализированные глобальные данные» из нескольких процессов и сохраняет их вместе? – Noidea

+7

@Noidea Различные процессы имеют разные адресные пространства. 0xDEADBEEF в одном процессе (обычно) полностью не связан с 0xDEADBEEF в другом. Есть некоторые очевидные незначительные преимущества для вышеупомянутого макета, связанные с отладкой и блокировкой роста (особенно для кучного блока, хотя не очень важно фрагментировать кучу с помощью mmap, если она больше не может расти). Кроме того, фактические сопоставленные адреса обычно будут случайными по соображениям безопасности. – PSkocik

+6

@Noidea: не связывать физические адреса (соответствующие адресам в ОЗУ) с адресами виртуальной памяти (адреса в процессе). Это задача [блока управления памятью] (https://en.wikipedia.org/wiki/Memory_management_unit) для преобразования виртуального в физическое, а все адреса, используемые процессом, транслируются через MMU. Каждый процесс имеет свои собственные MMU-таблицы, управляемые ОС. –

0
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. 
+1

Это, похоже, не отвечает на заданный вопрос. Напоминаем, что вопрос задал вопрос: «Описана ли ОС специально?» Есть ли какие-либо преимущества в этом отношении? Ваш ответ, похоже, не затрагивает эти вопросы. Вы хотите отредактировать свой ответ, чтобы больше узнать, что было задано? –

16

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

7

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

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 указует на аналогичную область памяти.

+3

Нет, 'h' уже является указателем на символ, поэтому' & h' ничего не делает. Запись 'h' и' & i' верна, так как оба являются адресами указанной строки и 'int' соответственно. –

+1

@underscore_d Я думаю, вы полностью неправильно поняли вопрос и мой ответ. Нет ничего «правильного» или «неправильного» в написании «h» и «&i';», который был просто озадачен, почему фактические адреса в его системе были настолько разными. Я хотел сказать, что если вы напишете '& h' и' & i', или 'h' и' ip', вы, скорее всего, увидите более похожие адреса, и это упражнение (надеюсь) поможет вам понять, почему числа в 'h' и '& i' настолько разные. –

+3

@SteveSummit Указатель на строковый литерал будет другой переменной стека. Но мне было интересно, почему адрес строкового литерала настолько отличается от адресов переменных стека. Не почему адреса двух переменных стека похожи;) – Noidea

1

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

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

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

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

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

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

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

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

Смежные вопросы