2016-07-24 36 views
4

Beginnen wir mit einem Beispiel, ich denke, das wird das Problem demonstrieren, mit dem ich mich sofort beschäftige. Dies ist ein einfaches Testprogramm, weit von realistischen, aber es funktioniert sehr gut, das Problem veranschaulichtWie man Valgrind mit einer Funktion verwendet, die eigentlich die Erweiterung eines Makros ist

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 } 

Wenn Sie diesen Code kompilieren und

valgrind --show-origins=yes ./compiled-program 

verwenden Sie ein ouput wie

==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) 
sehen

Wie Sie in der obigen Valgrind-Ausgabe sehen können, stammt der erste nicht initialisierte Lesevorgang von der my_test_function()-Funktion und zeigt die genaue Zeile whe an Das Problem ist aufgetreten. Auf diese Weise ist es ziemlich einfach, den Code zu reparieren. Die anderen Berichte sind offensichtlich nicht zu verstehen. Das Beste, was Sie damit tun können, ist zu wissen, welche Funktion es war, aber das ist alles.

Ich verstehe, dass der generierte Code verwirrend valgrind ist und deshalb meine eigentliche Frage ist,

  • Gibt es eine Möglichkeit, den Code mit gcc zu kompilieren, die diese Art von Funktionen valgrind verstehen helfen kann?
+0

Beenden Sie die Verwendung von Makros und vertrauen Sie dem Compiler zum Inline-Schreiben? Hand-erweitern in den wichtigen Fällen, nur um die Daten zu bekommen, die Sie brauchen? Microbenchmark nur der Mut des Makros? – bmargulies

+1

@bmargulies Das Makro emuliert C++ - Vorlagen. Es hat nichts mit Leistung zu tun. – user3386109

Antwort

3

Ich würde sagen, es ist unwahrscheinlich, dass die Funktion, die Sie suchen, existiert. Um zu verstehen, warum, um den Code zu diesem ändern:

#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__); 
} 

Der Ausgang

test 24 
test 25 
int 19 
int 19 
float 20 
float 20 

Der Punkt sein wird, ist, dass der Compiler my_int_function als eine einzige Codezeile sieht, als ob Sie es wie folgt geschrieben

void my_int_function(struct struct_name *object, type value) { printf("%s %d\n", "int", __LINE__); printf("%s %d\n", "int", __LINE__); } 

die Umwandlung von mehrzeiligen Makro Code einzelne Zeile wird vom Präprozessor ausgeführt, so durch die Zeit der Compiler um Zahlen zuweisen Zeile bekommt, Ihre Funktion ist schon eine einzige Codezeile.

In der Tat können Sie mit -E kompilieren, und sehen, was der Präprozessor tat.

Als Referenz finden Sie im Abschnitt 5.1.1.2 Übersetzungsphasen im C11-Entwurfsspezifikation.

Der Vorrang unter den Syntaxregeln der Übersetzung wird durch die folgenden Phasen angegeben. 6)

  1. Physical Datei Mehrbyte-Zeichen Quelle abgebildet werden, in einer implementierungs definiert, um das Ausgangszeichen Satz (new-line Zeichen Einführung für End-of-line-Indikatoren), wenn erforderlich. Trigraph-Sequenzen werden durch entsprechende interne Ein-Zeichen-Repräsentationen ersetzt.

  2. Jede Instanz eines umgekehrten Schrägstrichs(), unmittelbar gefolgt von einem neuen Zeilenzeichen, wird gelöscht, indem physische Quellzeilen an logische Quellzeilen gespleißt werden. [...]

6) Implementationen werden als verhalten, wenn diese getrennten Phasen auftreten, obwohl viele der Regel in der Praxis zusammen gefaltet werden. [...]

+1

Ich habe keine Ahnung, warum es mir nicht eingefallen ist, 'gcc -E 'auszuführen, und ich denke, dass ich nicht dachte, der Präprozessor sei verantwortlich, aber es ist verantwortlich. –

5

ich diese Art von Makro-induzierten Debuggen Problem lösen, indem sie:

1/Kommentar aus Standardbibliothek/Dritten #includes

2/durch gcc -E passieren -C -P, die die Makros

3/legte die #includes zurück

4/Pass durch Klirren-Format, das die sehr langen Linien aufbricht erweitert

5/Kompilieren mit Debugging-Informationen

Das Programm ist genau das gleiche wie zuvor, aber gdb und valgrind beziehen sich auf die erweiterte Quelle. Es ist dann ziemlich einfach, den Fehler zu finden und dann mit einem Diff-Tool zurück zur ursprünglichen Quelle zu führen.

Das obige klingt wie ein Schmerz, aber die Schritte 1 bis 4 sind genauso skriptfähig wie Schritt 5, so dass der tatsächliche Overhead während der Entwicklung minimal ist. Der Grund, warum dies nicht mein Standard ist, ist, dass der Sprung zum Fehler in einer IDE mich zum generierten Code bringt, der normalerweise irritierend ist.

+0

Das ist in der Tat eine Menge Ärger. Vielleicht wäre es einfacher, den Fehler zu finden, wenn Sie verstehen, warum der Code falsch ist. –

+0

Es war langweilig, das erste Mal zu schreiben, aber jetzt ist es nur ein weiteres Build-Ziel. 'make expand'. Nützlich, um herauszufinden, wie Makros interagieren. –