如何将 valgrind 与实际上是宏扩展的函数一起使用
How to use valgrind with a function that is actually the expansion of a macro
让我们从一个例子开始,我认为这将立即说明我正在处理的问题。这是一个简单的测试程序,与现实相差甚远,但确实很好地说明了问题
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 struct first {
5 int i_value;
6 };
7
8 struct second {
9 float f_value;
10 };
11
12 #define DEFINE_FUNCTION(type, struct_name, field_name) \
13 void my_ ## type ## _function(struct struct_name *object, type value) \
14 { \
15 /* Deliberately read an uninitialized value to make valgrind */ \
16 /* report the issue */ \
17 if (object->field_name == -1) \
18 return; \
19 object->field_name = value; \
20 }
21
22 DEFINE_FUNCTION(int, first, i_value);
23 DEFINE_FUNCTION(float, second, f_value);
24
25 void
26 my_test_function(struct first *object, int value)
27 {
28 /* Deliberately read an uninitialized value to make valgrind */
29 /* report the issue */
30 if (object->i_value == -1)
31 return;
32 object->i_value = value;
33 }
34
35 int
36 main(void)
37 {
38 struct first frst;
39 struct second scnd;
40
41 my_test_function(&frst, -5);
42 my_int_function(&frst, -2);
43 my_float_function(&scnd, 3.0);
44
45 return 0;
46 }
如果你编译这段代码并使用
valgrind --show-origins=yes ./compiled-program
你会看到这样的输出
==25304== Memcheck, a memory error detector
==25304== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==25304== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==25304== Command: ./macro-valgrind
==25304==
==25304== Conditional jump or move depends on uninitialised value(s)
==25304== at 0x40056F: my_test_function (macro-valgrind.c:30)
==25304== by 0x400597: main (macro-valgrind.c:41)
==25304== Uninitialised value was created by a stack allocation
==25304== at 0x40057F: main (macro-valgrind.c:37)
==25304==
==25304== Conditional jump or move depends on uninitialised value(s)
==25304== at 0x40053A: my_float_function (macro-valgrind.c:23)
==25304== by 0x4005BC: main (macro-valgrind.c:43)
==25304== Uninitialised value was created by a stack allocation
==25304== at 0x40057F: main (macro-valgrind.c:37)
==25304==
==25304== Conditional jump or move depends on uninitialised value(s)
==25304== at 0x400547: my_float_function (macro-valgrind.c:23)
==25304== by 0x4005BC: main (macro-valgrind.c:43)
==25304== Uninitialised value was created by a stack allocation
==25304== at 0x40057F: main (macro-valgrind.c:37)
==25304==
==25304==
==25304== HEAP SUMMARY:
==25304== in use at exit: 0 bytes in 0 blocks
==25304== total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==25304==
==25304== All heap blocks were freed -- no leaks are possible
==25304==
==25304== For counts of detected and suppressed errors, rerun with: -v
==25304== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 0 from 0)
正如您在上面的 valgrind 输出中看到的,报告的第一个未初始化读取来自 my_test_function()
函数,它显示了发生问题的确切行。这样修复代码就相当容易了。其他报道显然无法理解。你能用它们做的最好的事情就是知道它是哪个函数,但仅此而已。
我知道生成的代码令人困惑 valgrind 这就是为什么我的实际问题是,
- 有没有办法用gcc编译代码可以帮助valgrind理解这种函数?
我会说您寻找的功能不太可能存在。要了解原因,请将代码更改为:
#define DEFINE_FUNCTION(type, struct_name, field_name) \
void my_ ## type ## _function(struct struct_name *object, type value) \
{ \
printf( "%s %d\n", # type, __LINE__ ); \
printf( "%s %d\n", # type, __LINE__ ); \
}
DEFINE_FUNCTION(int, first, i_value);
DEFINE_FUNCTION(float, second, f_value);
void my_test_function(struct first *object, int value)
{
printf( "test %d\n", __LINE__ );
printf( "test %d\n", __LINE__ );
}
输出将是
test 24
test 25
int 19
int 19
float 20
float 20
重点是编译器把my_int_function
看成一行代码,就好像你是这样写的
void my_int_function(struct struct_name *object, type value) { printf( "%s %d\n", "int", __LINE__ ); printf( "%s %d\n", "int", __LINE__ ); }
从多行宏到单行代码的转换是由预处理器执行的,因此当编译器开始分配行号时,您的函数已经是单行代码了。
事实上你可以用-E
编译,看看预处理器做了什么。
有关参考,请参阅 C11 规范草案中的 5.1.1.2 翻译阶段 部分。
The precedence among the syntax rules of translation is specified by
the following phases.6)
Physical source file multibyte characters are mapped, in an implementation- defined manner, to the source character set
(introducing new-line characters for end-of-line indicators) if
necessary. Trigraph sequences are replaced by corresponding
single-character internal representations.
Each instance of a backslash character () immediately followed by a new-line character is deleted, splicing physical source lines to
form logical source lines. [...]
6) Implementations shall behave as if these separate phases occur,
even though many are typically folded together in practice. [...]
我通过以下方式解决了这种由宏引起的调试问题:
1/注释掉标准库/第三方#includes
2/通过gcc -E -C -P,展开宏
3/ 把#includes 放回去
4/通过clang-format,打断很长的行
5/编译带调试信息
程序和以前一样,只是gdb和valgrind引用了扩展源。然后很容易找到错误,然后使用 diff 工具将其追溯到原始来源。
以上听起来很麻烦,但步骤 1 到 4 与步骤 5 一样可编写脚本,因此开发期间的实际开销很小。这不是我的默认设置的原因是 ide 中的错误跳转到生成的代码,这通常很烦人。
让我们从一个例子开始,我认为这将立即说明我正在处理的问题。这是一个简单的测试程序,与现实相差甚远,但确实很好地说明了问题
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 struct first {
5 int i_value;
6 };
7
8 struct second {
9 float f_value;
10 };
11
12 #define DEFINE_FUNCTION(type, struct_name, field_name) \
13 void my_ ## type ## _function(struct struct_name *object, type value) \
14 { \
15 /* Deliberately read an uninitialized value to make valgrind */ \
16 /* report the issue */ \
17 if (object->field_name == -1) \
18 return; \
19 object->field_name = value; \
20 }
21
22 DEFINE_FUNCTION(int, first, i_value);
23 DEFINE_FUNCTION(float, second, f_value);
24
25 void
26 my_test_function(struct first *object, int value)
27 {
28 /* Deliberately read an uninitialized value to make valgrind */
29 /* report the issue */
30 if (object->i_value == -1)
31 return;
32 object->i_value = value;
33 }
34
35 int
36 main(void)
37 {
38 struct first frst;
39 struct second scnd;
40
41 my_test_function(&frst, -5);
42 my_int_function(&frst, -2);
43 my_float_function(&scnd, 3.0);
44
45 return 0;
46 }
如果你编译这段代码并使用
valgrind --show-origins=yes ./compiled-program
你会看到这样的输出
==25304== Memcheck, a memory error detector
==25304== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==25304== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==25304== Command: ./macro-valgrind
==25304==
==25304== Conditional jump or move depends on uninitialised value(s)
==25304== at 0x40056F: my_test_function (macro-valgrind.c:30)
==25304== by 0x400597: main (macro-valgrind.c:41)
==25304== Uninitialised value was created by a stack allocation
==25304== at 0x40057F: main (macro-valgrind.c:37)
==25304==
==25304== Conditional jump or move depends on uninitialised value(s)
==25304== at 0x40053A: my_float_function (macro-valgrind.c:23)
==25304== by 0x4005BC: main (macro-valgrind.c:43)
==25304== Uninitialised value was created by a stack allocation
==25304== at 0x40057F: main (macro-valgrind.c:37)
==25304==
==25304== Conditional jump or move depends on uninitialised value(s)
==25304== at 0x400547: my_float_function (macro-valgrind.c:23)
==25304== by 0x4005BC: main (macro-valgrind.c:43)
==25304== Uninitialised value was created by a stack allocation
==25304== at 0x40057F: main (macro-valgrind.c:37)
==25304==
==25304==
==25304== HEAP SUMMARY:
==25304== in use at exit: 0 bytes in 0 blocks
==25304== total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==25304==
==25304== All heap blocks were freed -- no leaks are possible
==25304==
==25304== For counts of detected and suppressed errors, rerun with: -v
==25304== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 0 from 0)
正如您在上面的 valgrind 输出中看到的,报告的第一个未初始化读取来自 my_test_function()
函数,它显示了发生问题的确切行。这样修复代码就相当容易了。其他报道显然无法理解。你能用它们做的最好的事情就是知道它是哪个函数,但仅此而已。
我知道生成的代码令人困惑 valgrind 这就是为什么我的实际问题是,
- 有没有办法用gcc编译代码可以帮助valgrind理解这种函数?
我会说您寻找的功能不太可能存在。要了解原因,请将代码更改为:
#define DEFINE_FUNCTION(type, struct_name, field_name) \
void my_ ## type ## _function(struct struct_name *object, type value) \
{ \
printf( "%s %d\n", # type, __LINE__ ); \
printf( "%s %d\n", # type, __LINE__ ); \
}
DEFINE_FUNCTION(int, first, i_value);
DEFINE_FUNCTION(float, second, f_value);
void my_test_function(struct first *object, int value)
{
printf( "test %d\n", __LINE__ );
printf( "test %d\n", __LINE__ );
}
输出将是
test 24
test 25
int 19
int 19
float 20
float 20
重点是编译器把my_int_function
看成一行代码,就好像你是这样写的
void my_int_function(struct struct_name *object, type value) { printf( "%s %d\n", "int", __LINE__ ); printf( "%s %d\n", "int", __LINE__ ); }
从多行宏到单行代码的转换是由预处理器执行的,因此当编译器开始分配行号时,您的函数已经是单行代码了。
事实上你可以用-E
编译,看看预处理器做了什么。
有关参考,请参阅 C11 规范草案中的 5.1.1.2 翻译阶段 部分。
The precedence among the syntax rules of translation is specified by the following phases.6)
Physical source file multibyte characters are mapped, in an implementation- defined manner, to the source character set (introducing new-line characters for end-of-line indicators) if necessary. Trigraph sequences are replaced by corresponding single-character internal representations.
Each instance of a backslash character () immediately followed by a new-line character is deleted, splicing physical source lines to form logical source lines. [...]
6) Implementations shall behave as if these separate phases occur, even though many are typically folded together in practice. [...]
我通过以下方式解决了这种由宏引起的调试问题:
1/注释掉标准库/第三方#includes
2/通过gcc -E -C -P,展开宏
3/ 把#includes 放回去
4/通过clang-format,打断很长的行
5/编译带调试信息
程序和以前一样,只是gdb和valgrind引用了扩展源。然后很容易找到错误,然后使用 diff 工具将其追溯到原始来源。
以上听起来很麻烦,但步骤 1 到 4 与步骤 5 一样可编写脚本,因此开发期间的实际开销很小。这不是我的默认设置的原因是 ide 中的错误跳转到生成的代码,这通常很烦人。