"指向int问题的指针"

Mic*_*chi 1 c malloc free valgrind gcc8

今天我尝试从这里解决一个测验,当我到达问题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 );
}
Run Code Online (Sandbox Code Playgroud)

问题是:

在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
Run Code Online (Sandbox Code Playgroud)

因为这条线:

free(*ppInt2);
Run Code Online (Sandbox Code Playgroud)

根据我的理解表明,没有编译或运行时错误,我决定

free(*ppInt2)
Run Code Online (Sandbox Code Playgroud)

是不正确的.

但是因为这里没有编译/运行时错误,所以使Answers BC错误.

作者说接受的答案是:

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

现在这是我的问题,为什么没有问题,因为这样做:

free( *ppInt2 );
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

我认为正确的free电话应该是:

free( ppInt2 );
Run Code Online (Sandbox Code Playgroud)

测试Linux mint 19,GCC-8valgrind-3.13.0

Ste*_*mit 7

答案C最接近正确.这条线

free( *ppInt2 );
Run Code Online (Sandbox Code Playgroud)

肯定不对.该错误不是编译器可以检测到的错误.但它很可能会导致运行时错误.(但不保证会导致运行时错误.更多信息如下.)

规则mallocfree非常简单:你指向的每个指针free必须是你从之前的malloc(或calloc,或realloc)调用中获得的指针.在代码中,malloc并且free要求pIntppInt1正确遵循此规则.但是,对于ppInt2,返回的指针malloc被赋值给ppInt2,但指针传递给的free是指向*ppInt2的值ppInt2.但是,因为*ppInt2- 也就是说 - 指向的值ppInt2没有以任何方式初始化,它是一个垃圾值,free可能会崩溃.最终的结果或多或少就像你说的那样

int main()
{
    int *p;
    free(p);     /* WRONG */
}
Run Code Online (Sandbox Code Playgroud)

但是,再一次,崩溃并不能保证.因此,更正确的答案将被表述为

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编程相当于在街上随时运行.然后这些程序员抱怨,好像这不是他们的错,当他们的运气不可避免地耗尽并且发生了可怕的致命事故.