如何将 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 这就是为什么我的实际问题是,

我会说您寻找的功能不太可能存在。要了解原因,请将代码更改为:

#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)

  1. 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.

  2. 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 中的错误跳转到生成的代码,这通常很烦人。