在这里学习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有关。)
感谢您的帮助!
答案 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)。