Защо 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? Чудесата на UB :)   -  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 ли е? Малко вероятно. (BTW, argv не трябва да бъде const и buf2 трябва да бъде нулев край.)   -  person ooga    schedule 28.04.2014
comment
@MartinJames: Да...   -  person Ed S.    schedule 28.04.2014
comment
Очевидно и двата указателя сочат към едно и също пространство в купчината - те сочат към едно и също пространство в купчината, доколкото и двамата сочат към една и съща купчина, но не сочат към едно и също място в нея. Какво ви даде тази идея?   -  person Crowman    schedule 28.04.2014
comment
@EdS. Допускането на някаква връзка между адресите на указателите е разумно при експлойт. Те обикновено не отговарят на стандартите!   -  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 (несъседни). Добавете многопоточност към микса и ще видите защо не трябва да приемате нищо за поведението на който и да е разпределител на памет, който не сте внедрили сами (и наистина, дори ако сте го внедрили сами). Долу ръцете от UB.   -  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 
|--------|--------|

Първият memset дава

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

като в него задава от началото на buf2 до края до 2.

Вторият memset дава:

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