【问题标题】:"Pointer to pointer to int issue"“指向 int 问题的指针”
【发布时间】:2018-12-14 23:21:51
【问题描述】:

今天我尝试解决来自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 );
}

问题是:

选择正确的陈述 w.r.t.以上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 上测试

【问题讨论】:

  • free( *ppInt2 ); 是未定义的行为,C 标准中没有运行时错误的概念。你的测验演员返回malloc 所以我给他们0。
  • 没有一个选项是正确的陈述
  • @Michi : 那么你的分析是正确的:)
  • @Michi - 为您省去另一个问题:下一个问题使用不正确的格式说明符来打印 sizeof 值。这使得所有 那些 的答案都是错误的。该站点可能不是学习 C 的最佳场所。:-)
  • “geeksforgeeks”一次又一次地被证明是一个垃圾网站,业余爱好者写文章,没有人校对。根本不要使用那个网站。

标签: c malloc valgrind free gcc8


【解决方案1】:

答案 C 最接近正确。线

free( *ppInt2 );

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

mallocfree 的规则非常简单:您传递给 free 的每个指针都必须与您从上一次调用 malloc(或 callocrealloc )。在代码中,mallocfree 调用 pIntppInt1 正确遵循此规则。但是对于ppInt2malloc返回的指针赋值给ppInt2,但是交给free的指针是*ppInt2ppInt2指向的值。但是由于*ppInt2——即ppInt2指向的值——还没有以任何方式初始化,它是一个垃圾值,free很可能会崩溃。最终结果或多或少与您所说的完全一样

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

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

C' - free(*ppInt2) 不正确。它可能会出现运行时错误。

恐怕说答案 D 正确的人可能并不真正知道他们在说什么。我建议不要继续这个测验——谁知道它包含多少其他错误或误导性的答案?

总是很难理解未定义的行为,因为未定义的行为意味着任何事情都可能发生,包括什么也没有。当有人说“我听说做 X 是未定义的,但我试过了,效果很好”,就像在说“我听说过马路很危险,但我试过了” ,而且效果很好。”

关于未定义行为的另一件事是您必须仔细考虑并理解它。根据定义,几乎没有任何语言翻译工具——没有 C 编译器或其他工具——可以保证会警告您。 必须知道什么是未定义的,以及因此要避开什么。你不能说“好吧,我的程序编译时没有错误或警告,而且它似乎可以工作,所以它一定是正确的。”换句话说,你不能试图将“正确与不正确”的决定强加给机器——你必须拥有这种区别。


但也许你知道这一切。或许真正的问题很简单,“如果答案 C 是正确的,那么程序如何因运行时错误而失败,实际上它又怎么可能反复不失败?这个问题有两个答案:

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

  2. 1234563 )。这绝对是 由 C 标准保证——你不应该永远依赖它——但在那些系统上,它很可能也可以得到保证。此外,在几乎所有系统上,所有位为 0 的指针值都是空指针。因此,同样仅在那些特定系统上,并且仅在malloc 第一次为您提供指向全新内存的指针时,代码ppInt2 = malloc(10 * sizeof(int*)) 将为您提供 10 个空指针。并且由于free 被定义为如果您传递一个空指针则什么都不做,在这种特定情况下,free(*ppInt2) 将永远不会失败,即使在运行时也是如此。 (也许这就是设置测验的人的想法,因为如果你做出这些额外的假设,所写的答案 C 基本上是不正确的,而答案 D 基本上是 -- 我不想承认这一点-- 或多或少准确。)

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

【讨论】:

  • 好吧,想象一下有多少人在做这个测验:))。
  • The error is not one that can be detected by the compiler :好吧,GCC 的有趣之处在于,当涉及到类似的事情时,它会自行决定。看看Here,当你用-O3编译同一个程序时。 ==>> total heap usage: 1 allocs, 0 frees, 80 bytes allocated 而不是 ==>>> total heap usage: 3 allocs, 2 frees, 164 bytes allocated
  • 为了完整性:free 也可以在不调用未定义行为的情况下为空指针调用。这是在许多情况下显示的代码不会导致运行时错误的主要原因(如果 ppInt2 恰好指向零初始化内存)。
  • @SanderDeDycker 啊,对。好点子。 (回到我的类比,有点像说“我听说在繁忙的街道上跑步很危险,但我在半夜尝试过,效果很好。我什至尝试了十次。”)
  • @SanderDeDycker 如果您添加更多信息Like Here,您将获得free's,但仍然是invalid free will take place
【解决方案2】:

让我们解决这个问题:

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

Valgrind 抓住了这一点,这是该工具的一个很好的卖点,但它不是 C 语言的一部分。

【讨论】:

  • 实际上,我想说 Valgrind 和 gcc 一样是 C 语言的一部分。
  • @SteveSummit 好吧,从某种意义上说,你应该使用 Valgrind,如果可以的话,当然,你可以(应该?:))这么说。但是,它确实不是 C(语言)的一部分,并且仅在少数平台上可用 - 而 C 无处不在 可用,而 gcc 可用于 很多平台。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-10
  • 1970-01-01
  • 1970-01-01
  • 2017-09-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多