Почему malloc вызывается для двух разных указателей одного и того же адресного пространства?

Приведенный ниже код взят из домашнего задания, посвященного эксплуатации переполнения кучи, которую я понимаю как концепцию. Чего я не понимаю, так это того, что именно происходит с malloc и указателями в этом примере кода. Очевидно, что оба указателя указывают на одно и то же место в куче, но почему? Разве malloc не зарезервирует место для buf1, а затем зарезервирует еще одно пространство для buf2?

int main(int argc, const char * argv[])
{

    int diff, size = 8;
    char *buf1, *buf2;
    buf1 = (char * )malloc(size);
    buf2 = (char *)malloc(size);
    diff = buf2-buf1;
    memset(buf2, '2', size);
    printf("BEFORE: buf2 = %s",buf2);
    memset(buf1, '1', diff +3);
    printf("AFTER: buf2 = %s", buf2);
    return 0;
}

Этот код производит вывод

BEFORE: buf2 = 22222222AFTER: buf2 = 11122222

Большое спасибо. :)


person jmeanor    schedule 28.04.2014    source источник
comment
Как это не AV/segfault? Чудеса УБ :)   -  person Martin James    schedule 28.04.2014
comment
С этим кодом немного не так. 1. Вы не передаете строки с нулевым завершением в printf. 2. Вы предполагаете, что существует некоторая надежная связь между адресами двух указателей. 3. Не приводить возвращаемое значение malloc.   -  person Ed S.    schedule 28.04.2014
comment
Как вы думаете, почему buf1 и buf2 указывают на одно и то же пространство? Если бы они были, то они были бы равны, а diff был бы равен 0. Является ли diff 0? Вряд ли. (Кстати, argv не должен быть константой, а buf2 должен заканчиваться нулем.)   -  person ooga    schedule 28.04.2014
comment
@MartinJames: Да...   -  person Ed S.    schedule 28.04.2014
comment
Очевидно, что оба указателя указывают на одно и то же место в куче — они указывают на одно и то же место в куче, поскольку они оба указывают на одну и ту же кучу, но они не указывают на одно и то же место в ней. Что натолкнуло вас на эту идею?   -  person Crowman    schedule 28.04.2014
comment
@ЭдС. Предполагать некоторую связь между адресами указателей в эксплойте разумно. Обычно они не соответствуют стандартам!   -  person ooga    schedule 28.04.2014
comment
@PaulGriffiths, почему результат изменения buf1 отображается при печати bf2 на втором printf?   -  person jmeanor    schedule 28.04.2014
comment
@jmeanor: потому что первый вызов malloc() выделяет для вас 8 байтов, а второй вызов malloc() выделяет следующие 8 байтов. Когда вы перезаписываете границы первого распределения, вы просто прошиваете и начинаете запись во второе распределение. Не гарантируется, что вы всегда будете получать непрерывные выделения, подобные этому, но если вы выделяете размеры блоков, которые кратны требованиям выравнивания, вы, вероятно, можете ожидать, что разумный распределитель памяти сделает это.   -  person Crowman    schedule 28.04.2014
comment
@PaulGriffiths, спасибо! Теперь я понимаю, я предполагал, что dif приводит к 0, но на самом деле это 8. Это был долгий семестр. :о)   -  person jmeanor    schedule 28.04.2014
comment
buf1 и buf2 указывают на два разных объекта. Вычитание двух указателей имеет неопределенное поведение.   -  person Keith Thompson    schedule 28.04.2014
comment
@PaulGriffiths: Вы не можете ожидать непрерывного распределения за пределами тривиального примера. Выделите блоки A и B и освободите блок A. Теперь выделите два блока, и все ставки сняты, получите ли вы C и D (смежные) или A и C (несмежные). Добавьте к этому многопоточность, и вы поймете, почему вам не следует делать никаких предположений о поведении любого распределителя памяти, который вы не реализовали сами (и действительно, даже если вы реализовали его самостоятельно). Руки прочь от УБ.   -  person DevSolar    schedule 28.04.2014
comment
@DevSolar: ничего не выпускается, и здесь нет многопоточности. Возможно, вы думаете о ком-то другом.   -  person Crowman    schedule 28.04.2014
comment
@PaulGriffiths: Здесь мы явно рассматриваем минимальный рабочий пример. Я считаю небрежным говорить что-то вроде цитаты, если вы выделяете размеры блоков, которые кратны требованиям к выравниванию, вы, вероятно, можете ожидать, что разумный распределитель памяти сделает это, конец цитаты и не учитывать, что люди могут принять это утверждение и применить его к нетривиальной среде. Я предпочитаю, чтобы однажды не пришлось выслеживать возникший беспорядок условий гонки в многопоточном приложении.   -  person DevSolar    schedule 28.04.2014
comment
@DevSolar: Сделать это означает сделать то, что произошло в случае с этой здесь программой в вопросе, и при указанных обстоятельствах вы, безусловно, можете ожидать, что разумный распределитель памяти сделает это, как мы видели. Это гарантировано? Нет. Но вы можете ожидать этого, и в большинстве случаев вы будете правы, ожидая этого. Можете ли вы ожидать этого или нет, совершенно не связано с вопросом о том, следует ли вам писать программы, которые полагаются на него, что никто, кроме вас, не предлагал. Проблема заключается в том, чтобы объяснить поведение в вопросе, не более того.   -  person Crowman    schedule 28.04.2014


Ответы (1)


Объяснение результата

buf1 и buf2 не указывают на одно и то же пространство.

Ваш результат можно объяснить следующим образом.

К счастью, распределение дает следующую схему памяти:

buf1      buf2 
|--------|--------|

Первый мемсет дает

buf1      buf2 
|--------|22222222|

как в нем устанавливается от начала buf2 до конца до 2.

Второй мемсет дает:

buf1      buf2 
|11111111|11122222|

То есть он устанавливает от начала buf1 до 3 после его конца.

Неопределенное поведение

Это не является ошибкой, поскольку вы меняете память, выделенную для вашей программы.

Однако передача buf2 в printf таким образом вызывает неопределенное поведение.

Причина в том, что printf задействован как:

printf("BEFORE: buf2 = %s",buf2);

не имеет способа узнать размер buf2, поэтому он продолжается до тех пор, пока не увидит нулевое значение \0 символа, который ваш код не добавляет. Кажется, по счастливой случайности вы получили значение сразу после того, как buf2 становится нулевым значением.

Вы можете либо добавить символ \0 в конец buf2.

Или, может быть, более подходящим в этом случае вы могли бы использовать спецификатор точного формата (это ., за которым следует значение int), чтобы сообщить printf, сколько символов нужно напечатать. Это будет сделано так:

printf("BEFORE: buf2 = %.8s",buf2); 
person PeterSW    schedule 28.04.2014
comment
Визуал объясняет это. Теперь это имеет смысл, большое спасибо! - person jmeanor; 28.04.2014
comment
Я ожидал segfault, потому что дампы незавершенных строк. - person Martin James; 28.04.2014
comment
@MartinJames ага, я понимаю, что ты имеешь в виду. Спасибо что подметил это! Я обновил ответ, чтобы объяснить и это. - person PeterSW; 28.04.2014