2016-12-13 1 views
0

На экзамене я недавно вернулся, у него возник вопрос о том, «если этот код скомпилируется, что он будет делать?»Какое влияние динамически выделяет 100 байтов для int *, а затем пытается установить для него значения, используя арифметику указателя?

Код:

int *ptr 

ptr = (int *) malloc(25 * sizeof(int)); //100 bytes 

*ptr = 'x'; 
*(ptr + 1) = 'x'; 
.... //go through all the values from 1 to 99 as well 
*(ptr +99) = 'x'; 

Я написал код и запустить его, и результат при печати с printf(%d, *x) составляет 120, значение ASCII х. Я понимаю, что int должен быть просто установлен в x, и когда это печатается как int, значение ascii печатается, но я в тупике, когда дело доходит до фактического эффекта malloc, и что все из *(ptr + i) на самом деле.

+2

[Пожалуйста, просмотрите эту дискуссию о том, почему бы не использовать возвращаемое значение 'malloc()' и family в 'C'.] (Http://stackoverflow.com/q/605845/2173917). –

+2

malloc не обязательно выделяет 100 байтов, он выделяет '25 * sizeof (int)' и '* (ptr + n)', потому что выделенная память смежна, поэтому '* (ptr + 0)' даст вам int на первое местоположение '* (ptr + 1)' второе и так далее. Это очень похоже на доступ к массивам C, и вы можете использовать тот же синтаксис. I.E '* (ptr + 0)' совпадает с 'ptr [0]' – George

+1

hint: '* (ptr + 1)' такой же, как 'ptr [1]'. – dbush

ответ

2

В C массивы и указатели очень похожи, и для простоты в этом случае удобно думать о них как о том же. Таким образом, вы можете думать об этом malloc как о динамическом распределении массива из 25 целых чисел (например, то же самое, что сказать int ptr[25] динамически), или вы можете думать о нем как о блокировке 25 последовательных целочисленных адресов в памяти и маркировке их как действительных. Таким образом, ptr == &ptr[0]. Оператор разыменования, *, означает «изменить значение, сохраненное по этому адресу», и по существу «отменяет» оператор &. Итак, *ptr == *(&ptr[0]) == ptr[0]. Эта команда просто устанавливает ptr [0] равным «x», который имеет значение ASCII 120 (и будет печататься как значение ASCII, потому что массив имеет тип «int», а не тип «char»). Остальные задания также делают это. В зависимости от вашего компилятора и вашей операционной системы все, что находится над ptr + 24, скорее всего, даст вам ошибку сегментации или неправильную запись, потому что вы выделили только 25 целых чисел, и поэтому (ptr + 99) не должен быть записываемым адресом. Вы не сможете редактировать ptr[99], если вы выделили только 25 слотов.

+1

Массивы и указатели во многих случаях похожи, но они [** не то же самое **] (http://c-faq.com/aryptr/). Некоторые примеры http://stackoverflow.com/q/1704407/995714 http://stackoverflow.com/q/39444244/995714 –

+0

Вы правы. Для простоты в этом примере удобно думать о них как о том же, поскольку они действуют очень аналогично. Я обновил ответ, чтобы отразить вашу точку зрения. Спасибо – AndyW

1

Фактический эффект из malloc() является, что делает заявление *ptr = 'x'; и subsequents доступ фактически действительным.

Без выделения памяти попытка разыменования указателя вызовет undefined behavior.

Это говорит,

  • вы должны проверить для успеха malloc() перед попыткой разыменования возвращенного указателя.
  • указатель арифметический отличит тип данных. поэтому, и выражение, подобное (ptr + 1), указывает на ячейку памяти для следующего целое число, а не следующее байт памяти. Итак, что-то n > 24 для RHS выражения (ptr + <n>) вызовет UB.
  • Предположение 25 * sizeof(int) == 100 bytes очень специфично для реализации. В случае, если sizeof(int) меньше 4 байтов, вы получите доступ к внешней памяти в арифметике указателя (даже если вы указали указатель на char*, учитывая).
+4

В OP's cpde 'ptr [25]' UB, независимо от размера 'int' –

+0

@MichaelWalz Просто накрыл это во второй пуле. –

0

Oups, C указатель арифметика основан на определении *(ptr + i)являетсяptr[i].

Это означает, что при распределении пространства для 25 целых объектов доступ через 24-й элемент будет вызывать Undefined Behavior - вы фактически пытаетесь получить доступ к памяти, в которой вы не знаете, что она представляет.

Но ему разрешен доступ к любому объекту на уровне байта, если вы используете указатель на char (или на неподписанный символ). Так если предположить, что в вашем компиляторе SizeOf (INT) является 4, это хорошо:

int *iptr; 
char *cptr; 
iptr = malloc(25 * sizeof(int)); //100 bytes since we know that sizeof(int) is 4 
cptr = (char *) iptr; // cast of pointer to any to pointer to char is valid 
for(int i=0; i<25*sizeof(int); i++) cptr[i] = 'x'; // store chars 'x' 
for(int i=0; i<25; i++) { 
    printf(" %x", (unsigned int) iptr[i]); // print the resulting ints in hexa 
} 
printf("\n"); 

Предполагая, что вы используете ASCII представление символов (довольно часто), вы должны получить 25 значений все равно 0x78787878, как 0x78 является ASCII код 'x'. Но эта часть неуказанная по стандарту и только реализация определена.

+0

Является ли cast 'cptr = (char *) iptr;' действительно необходимым? (Не могу вспомнить стандарт верхней части головы, но я уверен, что 'cptr = iptr;' работает) – UnholySheep

1

Я в тупике, когда дело доходит до того, что фактический эффект таНоса является

malloc вызова выделяет пространство для массива. Когда вы изначально объявлять ptr, это не инициализируется точка к действительной ячейке памяти:

 +---+ 
ptr: | | ----> ??? 
    +---+ 

Попытка чтения или записей через ptr в это время приведет к неопределенному поведению; ваш код может упасть напрямую, или он может как-то повредить хранилище, или может появиться для запуска без каких-либо проблем.

malloc вызов выделяет пространство из кучи (иначе, динамический пул памяти) и присваивает адрес первого элемента этого пространства к ptr:

 +---+ 
ptr: | | ---+ 
    +---+ | 
     ...  | 
     +------+ 
     | 
     V 
    +---+ 
    | | ptr[0] 
    +---+ 
    | | ptr[1] 
    +---+ 
     ... 

Обратите внимание, что (int *) бросок на malloc звонок не был необходим со времен стандарта 1989 года и фактически считается плохой практикой (под C89 он может маскировать ошибку). ИМО, лучший способ, чтобы написать malloc вызова является

T *p = malloc(N * sizeof *p); 

где T любого типа, и N этого количества элементов типа T вы хотите выделить. Поскольку выражение *p имеет тип T, sizeof *p равнозначно sizeof (T).

и что все на самом деле (ptr + i).

*(ptr + i) эквивалентно ptr[i], так

*ptr = 'x'; 
*(ptr + 1) = 'x'; 

эквивалентны написанию

ptr[0] = 'x'; 
ptr[1] = 'x'; 

Пожалуйста, обратите внимание, что

*(ptr +99) = 'x'; 

находится вне диапазона массива вас» выделено; вы выделяете достаточно места для 25 целых чисел.Опять же, эта операция (и любая операция *(ptr + i) = 'x';, где i больше 24) приведет к неопределенному поведению, и ваш код может привести к сбою, повреждению данных или иным образом.

Арифметика указателя учитывает направленный тип; ptr + 1 дает адрес следующего целочисленного объекта, следующего за номером ptr. Таким образом, если ptr составляет 0x8000 и sizeof (int) равно 4, то ptr + 1 дает 0x8004, не0x8001.

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