Строго говоря, чтобы ответить на вопрос в заголовке, ответ будет таков, что это зависит от реализации. Некоторые реализации могут выделять память, а другие - нет.
Хотя в вашем коде есть и другие проблемы, о которых я расскажу ниже.
Примечание: изначально это была серия комментариев, которые я сделал по вопросу. Я решил, что это слишком много для комментария, и переместил их к этому ответу.
Когда вы проверите вывод, вы увидите, что он напечатает некоторые числа, как и ожидалось, но последние - тарабарщина.
Я считаю, что в системах, использующих модель сегментированной памяти, выделения «округляются» до определенного размера. Т.е. если вы выделяете X байтов, ваша программа действительно будет владеть этими X байтами, однако вы также сможете (неправильно) пройти мимо этих X байтов на некоторое время, прежде чем ЦП заметит, что вы нарушаете границы, и отправит SIGSEGV.
Скорее всего, именно поэтому ваша программа не дает сбоев в вашей конкретной конфигурации. Обратите внимание, что выделенные вами 8 байтов будут охватывать только два int в системах, где sizeof (int)
равно 4. Остальные 24 байта, необходимые для других 6 int, не принадлежат вашему массиву, поэтому что угодно может записывать в это пространство, и когда вы читаете из него space, то вы получите мусор, если ваша программа сначала не выйдет из строя.
Цифра 6 важна. Запомни это на потом!
Волшебная часть состоит в том, что в результирующем массиве будут правильные числа внутри, printf на самом деле просто печатает каждое число в другой раз. Но это меняет массив.
Примечание. Следующее является предположением, и я также предполагаю, что вы используете glibc в 64-битной системе. Я собираюсь добавить это, потому что считаю, что это может помочь вам понять возможные причины, по которым что-то может казаться работающим правильно, хотя на самом деле неверно.
Причина, по которой это "магически правильно", скорее всего, связана с printf
получением этих чисел через va_args. printf
, вероятно, заполняет область памяти сразу за физической границей массива (поскольку vprintf выделяет память для выполнения операции "itoa", необходимой для печати i
). Другими словами, эти «правильные» результаты на самом деле просто мусор, который «кажется правильным», но на самом деле это именно то, что находится в ОЗУ. Если вы попытаетесь изменить int
на long
, сохранив 8-байтовое распределение, ваша программа с большей вероятностью выйдет из строя, потому что long
длиннее, чем int
.
Реализация malloc в glibc имеет оптимизацию, при которой она выделяет целую страницу из ядра каждый раз, когда заканчивается куча. Это делает его быстрее, потому что вместо того, чтобы запрашивать у ядра больше памяти при каждом выделении, оно может просто захватить доступную память из «пула» и создать еще один «пул», когда первый заполнится.
При этом, как и стек, указатели кучи malloc, поступающие из пула памяти, имеют тенденцию быть смежными (или, по крайней мере, очень близко друг к другу). Это означает, что вызовы malloc из printf, скорее всего, появятся сразу после 8 байтов, выделенных для вашего массива int. Однако независимо от того, как это работает, суть в том, что независимо от того, насколько «правильными» могут показаться результаты, на самом деле они просто мусор, и вы вызываете неопределенное поведение, поэтому нет никакого способа узнать, что произойдет, или программа будет делать что-то еще при других обстоятельствах, например, сбой или неожиданное поведение.
Итак, я попытался запустить вашу программу с printf и без него, и оба раза результаты были неверными.
# without printf
$ ./a.out
0 1 2 3 4 5 1041 0
По какой-то причине ничего не мешало держать в памяти 2..5
. Однако что-то мешало удерживать в памяти 6
и 7
. Я предполагаю, что это буфер vprintf, используемый для создания строкового представления чисел. 1041
будет текстом, а 0
будет нулевым ограничителем, '\0'
. Даже если это не результат vprintf, что-то пишет по этому адресу между заполнением и печатью массива.
# with printf
$ ./a.out
*** Error in `./a.out': free(): invalid next size (fast): 0x0000000000be4010 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x77725)[0x7f9e5a720725]
/lib/x86_64-linux-gnu/libc.so.6(+0x7ff4a)[0x7f9e5a728f4a]
/lib/x86_64-linux-gnu/libc.so.6(cfree+0x4c)[0x7f9e5a72cabc]
./a.out[0x400679]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7f9e5a6c9830]
./a.out[0x4004e9]
======= Memory map: ========
00400000-00401000 r-xp 00000000 08:02 1573060 /tmp/a.out
00600000-00601000 r--p 00000000 08:02 1573060 /tmp/a.out
00601000-00602000 rw-p 00001000 08:02 1573060 /tmp/a.out
00be4000-00c05000 rw-p 00000000 00:00 0 [heap]
7f9e54000000-7f9e54021000 rw-p 00000000 00:00 0
7f9e54021000-7f9e58000000 ---p 00000000 00:00 0
7f9e5a493000-7f9e5a4a9000 r-xp 00000000 08:02 7995396 /lib/x86_64-linux-gnu/libgcc_s.so.1
7f9e5a4a9000-7f9e5a6a8000 ---p 00016000 08:02 7995396 /lib/x86_64-linux-gnu/libgcc_s.so.1
7f9e5a6a8000-7f9e5a6a9000 rw-p 00015000 08:02 7995396 /lib/x86_64-linux-gnu/libgcc_s.so.1
7f9e5a6a9000-7f9e5a869000 r-xp 00000000 08:02 7999934 /lib/x86_64-linux-gnu/libc-2.23.so
7f9e5a869000-7f9e5aa68000 ---p 001c0000 08:02 7999934 /lib/x86_64-linux-gnu/libc-2.23.so
7f9e5aa68000-7f9e5aa6c000 r--p 001bf000 08:02 7999934 /lib/x86_64-linux-gnu/libc-2.23.so
7f9e5aa6c000-7f9e5aa6e000 rw-p 001c3000 08:02 7999934 /lib/x86_64-linux-gnu/libc-2.23.so
7f9e5aa6e000-7f9e5aa72000 rw-p 00000000 00:00 0
7f9e5aa72000-7f9e5aa98000 r-xp 00000000 08:02 7999123 /lib/x86_64-linux-gnu/ld-2.23.so
7f9e5ac5e000-7f9e5ac61000 rw-p 00000000 00:00 0
7f9e5ac94000-7f9e5ac97000 rw-p 00000000 00:00 0
7f9e5ac97000-7f9e5ac98000 r--p 00025000 08:02 7999123 /lib/x86_64-linux-gnu/ld-2.23.so
7f9e5ac98000-7f9e5ac99000 rw-p 00026000 08:02 7999123 /lib/x86_64-linux-gnu/ld-2.23.so
7f9e5ac99000-7f9e5ac9a000 rw-p 00000000 00:00 0
7ffc30384000-7ffc303a5000 rw-p 00000000 00:00 0 [stack]
7ffc303c9000-7ffc303cb000 r--p 00000000 00:00 0 [vvar]
7ffc303cb000-7ffc303cd000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
012345670 1 2 3 4 5 6 7 Aborted
Это интересная часть. Вы не упомянули в своем вопросе, произошел ли сбой вашей программы. Но когда я его запустил, он разбился. Жесткий.
Также неплохо проверить с valgrind, если он у вас есть. Valgrind - полезная программа, которая сообщает, как вы используете свою память. Вот результат valgrind:
$ valgrind ./a.out
==5991== Memcheck, a memory error detector
==5991== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==5991== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==5991== Command: ./a.out
==5991==
==5991== Invalid write of size 4
==5991== at 0x4005F2: make_array (in /tmp/a.out)
==5991== by 0x40061A: main (in /tmp/a.out)
==5991== Address 0x5203048 is 0 bytes after a block of size 8 alloc'd
==5991== at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==5991== by 0x4005CD: make_array (in /tmp/a.out)
==5991== by 0x40061A: main (in /tmp/a.out)
==5991==
==5991== Invalid read of size 4
==5991== at 0x40063C: main (in /tmp/a.out)
==5991== Address 0x5203048 is 0 bytes after a block of size 8 alloc'd
==5991== at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==5991== by 0x4005CD: make_array (in /tmp/a.out)
==5991== by 0x40061A: main (in /tmp/a.out)
==5991==
0 1 2 3 4 5 6 7 ==5991==
==5991== HEAP SUMMARY:
==5991== in use at exit: 0 bytes in 0 blocks
==5991== total heap usage: 2 allocs, 2 frees, 1,032 bytes allocated
==5991==
==5991== All heap blocks were freed -- no leaks are possible
==5991==
==5991== For counts of detected and suppressed errors, rerun with: -v
==5991== ERROR SUMMARY: 12 errors from 2 contexts (suppressed: 0 from 0)
Как видите, valgrind сообщает, что у вас есть invalid write of size 4
и invalid read of size 4
(4 байта - это размер int в моей системе). Также упоминается, что вы читаете блок размера 0, который следует за блоком размера 8 (блок, который вы заблокировали). Это говорит вам, что вы проходите мимо массива и попадаете в мусорную зону. Еще вы могли заметить, что он сгенерировал 12 ошибок из 2 контекстов. В частности, это 6 ошибок в контексте записи и 6 ошибок в контексте чтения. Ровно столько нераспределенного пространства, сколько я упоминал ранее.
Вот исправленный код:
#include <stdio.h>
#include <stdlib.h>
int *make_array(size_t n) {
int *result = malloc(n * sizeof (int)); // Notice the sizeof (int)
for (int i = 0; i < n; ++i)
result[i] = i;
return result;
}
int main() {
int *result = make_array(8);
for (int i = 0; i < 8; ++i)
printf("%d ", result[i]);
free(result);
return 0;
}
А вот вывод valgrind:
$ valgrind ./a.out
==9931== Memcheck, a memory error detector
==9931== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==9931== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==9931== Command: ./a.out
==9931==
0 1 2 3 4 5 6 7 ==9931==
==9931== HEAP SUMMARY:
==9931== in use at exit: 0 bytes in 0 blocks
==9931== total heap usage: 2 allocs, 2 frees, 1,056 bytes allocated
==9931==
==9931== All heap blocks were freed -- no leaks are possible
==9931==
==9931== For counts of detected and suppressed errors, rerun with: -v
==9931== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Обратите внимание, что он не сообщает об ошибках и результаты верны.
person
Braden Best
schedule
20.10.2016
printf()
- или многие другие<stdio.h>
функции - выделяют буфер, связанный сFILE *
, когда буфер требуется впервые, а не при создании файлового потока. Итак, краткий ответ на вопрос заголовка - Да. - person Jonathan Leffler   schedule 05.10.2016printf
? - person AnT   schedule 05.10.2016printf
, который вы упомянули,//printf("%d", i);
Вы просто печатаетеi
, а не буфер, так что это будет работать должным образом. - person Fantastic Mr Fox   schedule 05.10.2016printf
, то почему вас удивляет, что этотprintf
все печатает правильно? Этотprintf
просто печатаетi
напрямую. Он полностью не зависит от распределения памяти. - person AnT   schedule 05.10.2016