1

今天我试图从这里解决一个测验,当我到达问题 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 );
}

问题是:

在 C 程序上方选择正确的语句:

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)

是不正确的。

但是因为这里没有编译/运行时错误,所以会出现 AnswersBC错误。

作者说接受的答案是:

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 19GCC-8valgrind-3.13.0

4

2 回答 2

7

答案 C 最接近正确。线

free( *ppInt2 );

绝对是不正确的。该错误不是编译器可以检测到的。但它很可能会导致运行时错误。(但不保证会导致运行时错误。下面会详细介绍。)

malloc和的规则free非常简单:您传递的每个指针都free必须与您从上一次调用malloc(or calloc, or realloc) 中收到的指针完全相同。在代码中,mallocandfree要求pIntppInt1正确遵循此规则。但是对于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 标准绝对 不能保证这一点——你永远不应该依赖它——但是在那些系统上,它很可能也可以得到保证。此外,在几乎所有系统上,所有位为 0 的指针值都是空指针。所以,同样只有在那些特定的系统上,而且只有第一次malloc给你一个指向全新内存的指针,代码ppInt2 = malloc(10 * sizeof(int*))会给你 10 个空指针。并且由于free如果您将空指针传递给它,则定义为什么都不做,在这种特定情况下,free(*ppInt2)永远不会失败,即使在运行时也是如此。(也许这就是设置测验的人的想法,因为如果你做出这些额外的假设,那么所写的答案 C 基本上是不正确的,而答案 D 基本上是——我不想承认这一点——或多或少是准确的。 )

回到前面的类比,如果有人做了这些额外的假设,并注意到代码永远不会失败,并试图声称答案 D 是正确的,那基本上就像是在说,“我听说过马路很危险,但我半夜试了一下,效果还不错。我什至来回跑了十次,从来没有被车撞过,一次也没有。” 而且,不幸的是,有些程序员会遵循类似的逻辑,他们会编写类似于在任何时候跑过马路的 C 编程的程序。然后这些程序员抱怨,好像这不是他们的错,当他们的运气不可避免地耗尽并且发生可怕的致命崩溃时。

于 2018-07-06T11:12:30.297 回答
1

让我们整理一下:

  1. 这里没有编译时错误
  2. 这里也没有(指定的 ISO C 标准)运行时错误,因为 C 中几乎没有运行时错误。本质上,唯一的运行时错误是标准库函数的错误(返回)。
  3. free(*ppInt2)未定义的行为。任何事情都有可能发生。编译器可能会删除它,或者它甚至可以删除整个main(),或者更糟。如果它保持原样,free()函数本身也可能做任何事情——忽略、崩溃、报告错误、通过尝试释放给定的指针来搞乱它的簿记......
  4. 这是一个编码错误。不幸的是,就像 C 中的许多语言一样,它没有被语言编译器或其运行时/标准库捕获。

Valgrind 捕捉到这一点的事实是该工具的一个很好的卖点,但是,它不是 C 语言的一部分。

于 2018-07-06T11:24:38.033 回答