“指向int问题的指针”

时间:2018-07-06 10:58:00

标签: c malloc valgrind free gcc8

今天,我尝试解决Here中的一个测验,当我到达问题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)

不正确。

但是因为这里没有编译/运行时错误,所以答案BC都是错误的。

作者说接受的答案是:

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上进行了测试

2 个答案:

答案 0 :(得分:7)

答案C最接近正确的答案。线

free( *ppInt2 );

绝对不正确。该错误不是编译器可以检测到的错误。但这很可能导致运行时错误。 (但不能保证会导致运行时错误。下面更多内容。)

mallocfree的规则非常简单:您传递给free的每个指针必须与您先前对malloc的调用所收到的指针完全相同(或callocrealloc)。在代码中,mallocfree的调用pIntppInt1正确地遵循了此规则。但是对于ppInt2,将malloc返回的指针分配给ppInt2,但是传递给free的指针是*ppInt2,即{{ 1}}。但是由于ppInt2(即*ppInt2指向的值)尚未进行任何初始化,因此它是一个垃圾值,ppInt2可能会崩溃。最终结果或多或少与您说的完全一样

free

但是,同样不能保证崩溃。因此,更正确的答案将写为

  

C'-int main() { int *p; free(p); /* WRONG */ } 不正确。可能会给出运行时错误。

恐怕有人说答案D是正确的,可能真的不知道他们在说什么。我建议不要继续进行此测验-谁知道该测验还包含其他错误或误导性答案?

很难理解未定义的行为,因为未定义的行为意味着任何事情都可能发生,包括什么也没有发生。当有人说“我听说做 X 并没有定义,但是我尝试了,而且效果很好”时,就像在说“我听说在繁忙的街道上奔跑是危险的,但是我尝试了,效果很好。“

关于未定义行为的另一件事是,您必须仔细考虑并理解它。从定义上讲,几乎没有语言翻译工具(没有C编译器或其他工具)可以保证就此警告您。 必须知道什么是未定义的,因此要避免使用什么。您不能说:“好,我的程序编译时没有错误或警告,并且似乎可以正常运行,因此它必须正确。”换句话说,您不能试图将“正确与不正确”的判断强加给机器,而必须拥有这种区别。


但是也许您知道所有这些。也许真正的问题很简单,即“如果答案C是正确的,那么程序 not 怎么会由于运行时错误而失败,而实际上又怎么会反复失败呢?”这个问题有两个答案:

  1. 如前所述,未定义的行为意味着任何事情都可能发生,包括什么都没有(即没有错误),包括连续多次运行都没有的事情。

  2. 在许多系统上,free(*ppInt2)第一次为您提供一些崭新的内存的指针时,它始终是全零(即,或多或少地就像您被称为{ {1}})。这绝对是 C标准不能保证 –您永远不要 依赖它-但是在那些系统上,很有可能也可以保证它。此外,在几乎所有系统上,全为0的指针值是空指针。因此,仅在那些特定系统上,并且仅在malloc第一次给您提供指向全新内存的指针时,代码calloc将为您提供10个空指针。并且由于malloc被定义为如果您传递空指针,则不执行任何操作,因此在这种特定情况下,ppInt2 = malloc(10 * sizeof(int*))将永远不会失败,甚至在运行时也不会失败。 (也许这就是进行测验的人所想到的,因为 if 您做出了这些额外的假设,答案C基本上是不正确的,答案D基本上是-我讨厌承认这一点-或多或少准确。)

回到较早的类比,如果有人做出这些附加假设,并且注意到代码从未失败,并试图声称答案D是正确的,那基本上就像在说:“我听说过马路很危险。 ,但我在半夜尝试了一下,效果还不错。我什至来回跑了十次。我从来没有碰过汽车,甚至一次都没有。”而且,不幸的是,那里的程序员遵循类似的逻辑,他们将编写与C语言编程等效的程序,这些代码始终在街上运行。然后,这些程序员抱怨说,这不是他们的错,这是因为他们的运气不可避免地耗尽,并且发生了致命的致命崩溃。

答案 1 :(得分:1)

让我们来解决这个问题:

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

Valgrind抓住了这一事实,这对该工具来说是一个不错的卖点,但它不是C语言的一部分。