使用GLib解析命令行会导致Valgrind检测到无效读取

时间:2015-11-08 21:00:16

标签: c valgrind glib

在这里学习Valgrind,并学习如何更好地编写C.

我正在尝试使用GLib's command line parsing解析示例程序的命令行;事实上,几乎逐字逐句provided example。唯一的区别是我“弹出”argv的第一个元素并将其用作程序其余部分的命令;为了做到这一点,我跳过第一个参数并将其余参数复制到数组char **arguments

// file: main.c
int main(int argc, char **argv)
{
    const char *allowed_cmds[] = {"greet", "teerg"};
    char cmd[24];
    g_stpcpy(cmd, argv[1]);
    char **arguments= (char**)calloc((argc - 1), sizeof(char*));
    if (check_string_in_array(cmd, allowed_cmds, 2)) {
        skip_elements_from_array(argv, argc, 1, arguments);
    }
    char saluted[24];
    read_saluted_from_command_line(argc, arguments, saluted);
    free(arguments);
    // ... skipped ...
    return 0;
}

// file: hello.c
int read_saluted_from_command_line(int argc, char **argv, char *result)
{
    gchar *saluted = "world";

    GError *error = NULL;
    GOptionContext *context;
    GOptionEntry entries[] =
    {
      { "saluted", 's', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &saluted, "person or thing to salute", "WORLD" },
      { NULL }
    };
    context = g_option_context_new("- Say hello to a person or thing");
    g_option_context_add_main_entries(context, entries, NULL);
    if (!g_option_context_parse_strv(context, &argv, &error))
    {
        g_error("option parsing failed: %s\n", error->message);
        exit(1);
    }
    g_option_context_free(context);
    if (error != NULL)
        g_error_free(error);
    g_stpcpy(result, saluted);
    return 0;
}

此代码编译并运行正常,但使用Valgrind检查会导致:

$ valgrind --read-var-info=yes --track-origins=yes --leak-check=full ./hello greet
==7779== Memcheck, a memory error detector
==7779== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==7779== Using Valgrind-3.10.0 and LibVEX; rerun with -h for copyright info
==7779== Command: ./hello greet
==7779==
==7779== Invalid read of size 8
==7779==    at 0x4E9F303: g_strv_length (in /lib/x86_64-linux-gnu/libglib-2.0.so.0.4200.1)
==7779==    by 0x4E8B0AC: g_option_context_parse_strv (in /lib/x86_64-linux-gnu/libglib-2.0.so.0.4200.1)
==7779==    by 0x40128C: read_saluted_from_command_line (hello.c:54)
==7779==    by 0x401753: main (main.c:68)
==7779==  Address 0x597a298 is 0 bytes after a block of size 8 alloc'd
==7779==    at 0x4C2AD10: calloc (vg_replace_malloc.c:623)
==7779==    by 0x4016EE: main (main.c:58)

代码使用函数g_option_context_parse_strv,因为根据文档这个函数does not "assum[e] that the passed-in array is the argv of the main function"。使用g_option_context_parse会导致相同的消息。

我很确定违规变量是arguments,因为它在main:68中被精确分配,但我不明白为什么Valgrind认为"your program reads or writes memory at a place which Memcheck reckons it shouldn't"。更让我感到困惑的是,如果我从不同文件中的单独函数移动代码并将其直接粘贴到main.c中,则错误消失。将char **传递给函数是错误的吗?

(我在Stack Overflow上发现了几个讨论Valgrind和无效读取的线程,但它们都处理由OP定义的structs,并且没有任何与GLib有关。)

感谢您的帮助!

1 个答案:

答案 0 :(得分:1)

在我(我认为)答案之前:在寻求帮助时,您应该始终发布完整的片段,人们可以自行编译和运行(即SSCCE) 。此外,在查看valgrind日志时,确保发布一个完整的示例非常重要,这样人们就可以准确地看到警告的来源。

根据您发布的内容,问题是g_option_context_parse_strv需要以NULL结尾的数组。由于你还没有传递长度,这是glib知道数组是什么的唯一方法。实际上,由于它没有遇到NULL元素,因此glib将继续读取数组末尾的未初始化内存,这是valgrind(正确地)抱怨的地方。您需要为 vector <float> lines(test.rows); vector<vector<float> > colums(test.cols,lines); for(int i=0;i<colums.size(); i++) { for (int j=0;j<colums[i].size(); j++){ colums[i][j] = ((float)imagem.at<Vec3b>(j,i)[0]/(float)(imagem.at<Vec3b>(j,i)[0] + (float)imagem.at<Vec3b>(j,i) [1] + (float)imagem.at<Vec3b>(j,i) [2]))*255; aux = (int) floor(colums[i][j] + 0.5); colums[i][j] = aux; test.at<Vec3b>(j, i)[0] = aux; aux = 0; colums[i][j] = ((float)imagem.at<Vec3b>(j,i)[1]/ (float)(imagem.at<Vec3b>(j,i)[0] + (float)imagem.at<Vec3b>(j,i) [1] + (float)imagem.at<Vec3b>(j,i) [2]))*255; aux = (int) floor(colums[i][j] + 0.5); colums[i][j] = aux; test.at<Vec3b>(j, i)[1] = aux; aux = 0; colums[i][j] = ((float)imagem.at<Vec3b>(j,i)[2]/ (float)(imagem.at<Vec3b>(j,i)[0] + (float)imagem.at<Vec3b>(j,i) [1] + (float)imagem.at<Vec3b>(j,i) [2]))*255; aux = (int) floor(colums[i][j] + 0.5); colums[i][j] = aux; test.at<Vec3b>(j, i)[2] = aux; aux = 0; } } 中的额外元素分配空间并将其设置为NULL。

至于David关于glib和valgrind没有相处的评论,请记住这只是泄漏的情况非常重要,即便如此对于某些类型的泄漏。关于访问未初始化和/或无效内存的警告都是&#34;真实&#34;基于glib的程序和其他任何地方一样。如果不理解valgrind的输出(或AddressSanitizer,或其他类似工具),那将是危险的。

valgrind泄漏的限制是GLib为类型信息分配少量内存,类型信息由类型的每个实例共享。这些信息永远不会被释放,尽管它​​仍然可以访问(这就是为什么valgrind将其列为可能丢失,而不是绝对丢失)。基本上,您通常可以忽略有关arguments函数可能丢失的分配的警告,但这就是它。

原因信息永远不会被释放,因为这样做只会浪费性能。显然你需要知道何时来释放它,这意味着要跟踪它是否仍在使用中。这意味着跟踪垃圾收集器(它实际上不是C库的选项)或引用计数。引用计数需要在多个内核和缓存级别(以及可能的其他CPU)上保持计数器同步,这是巨大的性能消耗,并且非常不值得为了避免一些易于识别的误报在几个工具中(如valgrind和AddressSanitizer)。