«Указатель на указатель на выпуск int» - PullRequest
0 голосов
/ 06 июля 2018

Сегодня я попытался решить викторину из Здесь , и когда я дошел до Вопроса 3, появился следующий код:

#include <stdlib.h>

int main(void){
    int *pInt;
    int **ppInt1;
    int **ppInt2;

    pInt = (int*)malloc(sizeof(int));
    ppInt1 = (int**)malloc(10*sizeof(int*));
    ppInt2 = (int**)malloc(10*sizeof(int*));

    free( pInt );
    free( ppInt1 );
    free( *ppInt2 );
}

И вопрос был:

Выберите правильное утверждение w.r.t. вышеуказанная программа на С:

A - malloc() for ppInt1 and ppInt2 isn’t correct. It’ll give compile time error.
B - free(*ppInt2) is not correct. It’ll give compile time error.
C - free(*ppInt2) is not correct. It’ll give run time error.
D - No issue with any of the malloc() and free() i.e. no compile/run time error

Из-за этой строки:

free(*ppInt2);

Что, как я понимаю, говорит о том, что не будет никакой ошибки компиляции или времени выполнения, я решил, что

free(*ppInt2)

неверно.

Но поскольку здесь нет ошибок времени компиляции / выполнения, делает ответы B и C неправильными.

Автор говорит, что принятый ответ:

D - No issue with any of the malloc() and free() i.e. no compile/run time error.

Теперь вот мой вопрос, почему нет проблемы, потому что делать это:

free( *ppInt2 );

Отчеты Valgrind:

==9468== Memcheck, a memory error detector
==9468== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==9468== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==9468== Command: ./program
==9468== 
==9468== Conditional jump or move depends on uninitialised value(s)
==9468==    at 0x4C30CF1: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==9468==    by 0x1086C1: main (program.c:14)
==9468==  Uninitialised value was created by a heap allocation
==9468==    at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==9468==    by 0x108696: main (program.c:10)
==9468== 
==9468== 
==9468== HEAP SUMMARY:
==9468==     in use at exit: 80 bytes in 1 blocks
==9468==   total heap usage: 3 allocs, 2 frees, 164 bytes allocated
==9468== 
==9468== 80 bytes in 1 blocks are definitely lost in loss record 1 of 1
==9468==    at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==9468==    by 0x108696: main (program.c:10)
==9468== 
==9468== LEAK SUMMARY:
==9468==    definitely lost: 80 bytes in 1 blocks
==9468==    indirectly lost: 0 bytes in 0 blocks
==9468==      possibly lost: 0 bytes in 0 blocks
==9468==    still reachable: 0 bytes in 0 blocks
==9468==         suppressed: 0 bytes in 0 blocks
==9468== 
==9468== For counts of detected and suppressed errors, rerun with: -v
==9468== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

Я думал, что правильный free вызов должен быть:

free( ppInt2 );

Проверено на Linux mint 19, GCC-8 и valgrind-3.13.0

Ответы [ 2 ]

0 голосов
/ 06 июля 2018

Давайте разберемся с этим:

  1. Здесь нет ошибки времени компиляции
  2. Здесь нет также и ошибки времени выполнения (в соответствии со стандартом ISO C), поскольку рядом снет ошибок времени выполнения в C. По сути, единственными ошибками времени выполнения являются ошибки (возвращаемые) из стандартных библиотечных функций.
  3. free(*ppInt2) - неопределенное поведение .Все может случиться.Компилятор может удалить его, или он может полностью удалить main(), или, что намного хуже.Если он просто оставляет все как есть, сама функция free() может также делать что угодно - игнорировать, аварийно завершать работу, сообщать об ошибке, портить бухгалтерию, пытаясь освободить указанный указатель ...
  4. Это кодирование ошибка.К сожалению, как и многие в C, он не перехватывается языковым компилятором или его исполняющей / стандартной библиотекой.

Тот факт, что Valgrind ловит это, является хорошим моментом для продажи инструмента, но он не является частьюязыка Си.

0 голосов
/ 06 июля 2018

Ответ C наиболее близок к правильности. Линия

free( *ppInt2 );

определенно неверно. Ошибка не может быть обнаружена компилятором. Но это вполне может вызвать ошибку во время выполнения. (Но это не гарантированно приведет к ошибке во время выполнения. Подробнее об этом ниже.)

Правило для malloc и free довольно простое: каждый указатель, который вы вручаете free, должен быть точно таким, как вы получили от предыдущего вызова malloc (или calloc, или realloc ). В коде malloc и free требует pInt и ppInt1 правильно следовать этому правилу. Но для ppInt2 указатель, возвращаемый malloc, присваивается ppInt2, а указатель, передаваемый free, равен *ppInt2, значение, на которое указывает ppInt2. Но так как *ppInt2 - то есть значение, на которое указывает ppInt2, - никоим образом не инициализировано, это мусорное значение, при котором free может привести к сбою. Конечный результат более или менее точно такой, как если бы вы сказали

int main()
{
    int *p;
    free(p);     /* WRONG */
}

Но, опять же, сбой не гарантирован. Поэтому более правильный ответ будет сформулирован как

C '- free(*ppInt2) неверно. Вероятно, это даст ошибку во время выполнения.

Боюсь, что тот, кто говорит, что ответ D верен, может не знать, о чем он говорит. Я бы предложил не продолжать эту викторину - кто знает, сколько других неверных или вводящих в заблуждение ответов она содержит?

Всегда трудно понять неопределенное поведение, потому что неопределенное поведение означает, что может произойти все, что угодно , включая ничего. Когда кто-то говорит: «Я слышал, что выполнение X было неопределенным, но я попробовал это, и оно работало нормально», это все равно, что сказать: «Я слышал, что бегать по оживленной улице опасно, но я пытался, и все работало нормально. "

Еще одна вещь, связанная с неопределенным поведением, заключается в том, что Вы должны тщательно обдумать это и понять это . По определению, ни один инструмент языкового перевода - ни компилятор C, ни другой инструмент - гарантированно не предупредит вас об этом. Вы должны знать, что не определено и что следует избегать. Вы не можете сказать: «Ну, моя программа компилируется без ошибок и предупреждений, и, похоже, она работает, поэтому она должна быть правильной». Другими словами, вы не можете пытаться навязать «правильное или неправильное» определение на машине - вы должны владеть этим различием.


Но, возможно, вы знали все это. Возможно, реальный вопрос заключается в следующем: «Если ответ C верен, как программа , а не может завершиться с ошибкой во время выполнения, на самом деле, как она может многократно не работать?» На этот вопрос есть два ответа:

  1. Как уже упоминалось, неопределенное поведение означает все, что может произойти, в том числе ничего (т.е. без ошибок), в том числе ничего при нескольких последовательных запусках.

  2. Во многих системах первый раз malloc дает вам указатель на какую-то совершенно новую память, это всегда все биты-0 (то есть, более или менее, как если бы вы вызывали calloc ). Это абсолютно не гарантировано стандартами C - вы должны никогда не зависеть от него - но в этих системах вполне вероятно, что это может быть гарантировано. Кроме того, практически во всех системах значение указателя all-bits-0 является нулевым указателем. Итак, и снова только в этих конкретных системах, и снова только в первый раз malloc дает вам указатель на совершенно новую память, код ppInt2 = malloc(10 * sizeof(int*)) даст вам 10 нулевых указателей. А поскольку free определяется как ничего не делать, если вы передаете ему нулевой указатель, в этом конкретном случае free(*ppInt2) никогда не завершится ошибкой, даже во время выполнения. (Возможно, это именно то, что имел в виду человек, проводящий тест, потому что , если вы делаете эти дополнительные предположения, ответ C, как написано, в основном неверен, а ответ D в основном - я не хочу признавать это - - более или менее точно.)

Возвращаясь к более ранней аналогии, если кто-то делает эти дополнительные предположения и замечает, что код никогда не завершается с ошибкой, и пытается утверждать, что ответ D является правильным, то это все равно что сказать: «Я слышал, что бегать по улице было опасно, но я попробовал это посреди ночи, и это работало нормально. Я даже бегал туда-сюда десять раз. Я никогда не попадал под машину, даже ни разу ». И, к сожалению, есть программисты, которые следуют схожей логике и пишут программы, которые делают программирование на С эквивалентным работе через дорогу в любое время. И эти программисты потом жалуются, как будто это не их вина, когда неизбежно заканчивается их удача и происходит ужасный фатальный сбой.

...