Цитируется из ответа , связанного @tpr в комментариях, наблюдаемая вами разница в адресах обусловлена рандомизацией расположения адресного пространства :
Локальные переменные размещаются в стеке. Традиционно распределение стека повторяется, но в последние годы ситуация изменилась. Рандомизация размещения адресного пространства (ASR) - сравнительно недавнее нововведение в управлении памятью ОС, которое намеренно делает адреса памяти в распределениях стека (например, те, которые вы наблюдали) максимально недетерминированными во время выполнения. Это функция безопасности: это не позволяет плохим субъектам использовать переполнения буфера кучи, потому что, если реализация ASLR достаточно энтропийна, кто знает, что будет в конце переполненного буфера?
Важно, что ASLR применяется для выделения самого стека (вместе с другими областями данных, подключенными к исполняемому файлу). Как кратко сказано в Википедии:
Чтобы предотвратить надежный переход злоумышленника, например, к определенной эксплуатируемой функции в памяти, ASLR случайным образом размещает позиции в адресном пространстве ключевых областей данных процесса. , включая базу исполняемого файла и позиции стека, кучи и библиотек.
В разветвленном процессе адрес один и тот же: , а не из-за копирование при записи , как я первоначально ответил. Даже если вы измените переменную в разветвленном процессе, адрес останется прежним (хотя будет сделана копия переменной). Попробуйте запустить следующий код:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main() {
int a = 10;
int status;
printf("%d %p\n", a, &a);
pid_t pid = fork();
if (pid == 0)
{
printf("FORKED: %d %p\n", a, &a);
a = 11;
printf("FORKED: %d %p\n", a, &a);
return 0;
} else {
wait(&status);
printf("%d %p\n", a, &a);
return 0;
}
}
, и вы увидите, что a
изменяется только в разветвленном процессе, но родительский процесс напечатает его без изменений. Однако адрес остается одинаковым во всех напечатанных строках. При написании этого ответа это стало неожиданностью, поэтому после поиска я нашел этот вопрос . Ответ довольно прост:
Каждый отдельный процесс получает свое собственное 4G виртуальное адресное пространство, и задача менеджеров операционной системы и аппаратной памяти состоит в том, чтобы сопоставить ваши виртуальные адреса с физическимииз них.
Итак, хотя может показаться, что два процесса имеют один и тот же адрес для переменной, это всего лишь виртуальный адрес.
Диспетчер памяти отобразит его всовершенно другой физический адрес
Следующие две цитаты взяты из fork(2)
справочных страниц:
Дочерний процесс создается с помощью одного потока- тот, который называется fork (). Все виртуальное адресное пространство родительского объекта реплицируется в дочернем объекте, включая состояния мьютексов, переменных условий и других объектов pthreads
[...]
Под Linux fork ()реализуется с использованием страниц копирования при записи, поэтому единственное наказание, которое оно несет, - это время и память, необходимые для дублирования таблиц страниц родителя и создания уникальной структуры задач для дочернего элемента.
Из-за copy-on-write , упомянутого во второй цитате, базовый физический адрес может быть одинаковым для равного виртуального адреса памяти в разветвленном процессе и его родительском процессе. Из Википедии:
Копирование при записи ( CoW или COW ), иногда упоминаемое как неявное совместное использование или shadowing - это метод управления ресурсами, используемый в компьютерном программировании для эффективной реализации операции «дублировать» или «копировать» на изменяемых ресурсах. Если ресурс дублирован, но не изменен, нет необходимости создавать новый ресурс;ресурс может быть разделен между копией и оригиналом. Модификации все равно должны создавать копию, отсюда и техника: операция копирования откладывается до первой записи.
Следовательно, до тех пор, пока переменная не будет изменена (или не будет назван член семейства exec*
), одни и те же виртуальные адреса, скорее всего, будут соответствовать одному и тому же физическому адресу (исключения см. В man-страницах).