【问题标题】:Seeking assistance with Valgrind analysis of program寻求 Valgrind 程序分析方面的帮助
【发布时间】:2013-04-03 14:44:55
【问题描述】:

总而言之,我在棋盘程序中收到了一些有趣的错误。我使用~$ valgrind --track-origins=yes ./a.out 运行 Valgrind,并收到一些我难以解释的错误报告。

==10171== Conditional jump or move depends on uninitialised value(s)
==10171==    at 0x80488C2: diagnols.2353 (eq1.c:95)
==10171==    by 0x8048942: check.2372 (eq1.c:109)
==10171==    by 0x80485E6: recur (eq1.c:118)
==10171==    by 0x804872B: recur (eq1.c:151)
==10171==    by 0x804868C: recur (eq1.c:141)
==10171==    by 0x804868C: recur (eq1.c:141)
==10171==    by 0x804868C: recur (eq1.c:141)
==10171==    by 0x804868C: recur (eq1.c:141)
==10171==    by 0x804868C: recur (eq1.c:141)
==10171==    by 0x804868C: recur (eq1.c:141)
==10171==    by 0x804868C: recur (eq1.c:141)
==10171==    by 0x8048618: recur (eq1.c:123)
==10171==  Uninitialised value was created by a heap allocation
==10171==    at 0x402BB7A: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==10171==    by 0x804897A: main (eq1.c:159)

以下是代码中的一些实例:

  • 重复,第 123 行:

    if ( (result == 2)/*&&(width!=7)*/  ) {
      // go to next row, try first column
      puts("Option 1\n");
      recur(key, (depth + 1), 0);  // line 123
    } 
    
  • 重复,第 141 行:

    else if (  (result != 2)&&(width<7)  ) { // didn't work, try next column
      // go to next column, same row
      puts("Option 3\n");
      key->board[depth][width] = 0;
      recur(key, depth, (width+1));  // line 141
    
  • 诊断,第 95 行:

    while (  ((abc>=0)&&(bcd>=0) && (abc<=7)&&(bcd<=7))  ) {
      if (key->board[abc][bcd] == 1) {
    counter++;
      }
    

完整的诊断功能:

  int diagnols(int PARAMETER_ONE, int PARAMETER_TWO) { // returns 0 if good
    int abc = 0;
    int bcd = 0;
    int counter = 0;

    // first check diagnol up and to the left
    abc = PARAMETER_ONE-1;
    bcd = PARAMETER_TWO-1;
    while ( bcd>=0 && abc>=0 && abc<=7 && bcd<=7  ) {
      if (key->board[abc][bcd] == 1) {
        counter++;
      } abc--;
      bcd--;
    }

    // checking diagnol up and to the right
    abc = PARAMETER_ONE-1;
    bcd = PARAMETER_TWO+1;
    while ( abc>=0 && bcd>=0  && abc<=7 && bcd<=7  ) {
      if (key->board[abc][bcd] == 1) {
        counter++;
      } abc--;
      bcd++;
    }

    // checkign diagnol down and to the left
    abc = PARAMETER_ONE+1;
    bcd = PARAMETER_TWO-1;
    while ( abc>=0 && bcd>=0 && abc<=7 && bcd<=7  ) {
      if (key->board[abc][bcd] == 1) {
        counter++;
      } abc++;
      bcd--;
    }

    // checking diagnol down and to the right
    abc = PARAMETER_ONE+1;
    bcd = PARAMETER_TWO+1;
    while ( abc>=0 && bcd>=0 && abc<=7 && bcd<=7  ) {
      if (key->board[abc][bcd] == 1) {
        counter++;
      } abc++;
      bcd++;
    }

    return counter;
  }

原点追踪:

int main() {
  struct chessboard* master = malloc(sizeof(struct chessboard));

 /* zero out board */
 int one = 0;
 int two = 0;
 for (one; one <= 7; one++) {
   for (two; two <= 7; two++) {
     master->board[one][two] = 0;
   }
 }

结构是:

struct chessboard {
  int board[8][8];  // corrected
};

第二组错误

==10171== Invalid read of size 4
==10171==    at 0x8048767: vertical.2344 (eq1.c:51)
==10171==    by 0x804892B: check.2372 (eq1.c:108)
==10171==    by 0x80485E6: recur (eq1.c:118)
==10171==    by 0x804872B: recur (eq1.c:151)
==10171==    by 0x804868C: recur (eq1.c:141)
==10171==    by 0x804868C: recur (eq1.c:141)
==10171==    by 0x804868C: recur (eq1.c:141)
==10171==    by 0x804868C: recur (eq1.c:141)
==10171==    by 0x804868C: recur (eq1.c:141)
==10171==    by 0x804868C: recur (eq1.c:141)
==10171==    by 0x804868C: recur (eq1.c:141)
==10171==    by 0x8048618: recur (eq1.c:123)
==10171==  Address 0x41f8128 is 0 bytes after a block of size 256 alloc'd
==10171==    at 0x402BB7A: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==10171==    by 0x804897A: main (eq1.c:159)

显然出现了很多相同的行。

  • 垂直功能:

    int vertical(int PARAMETER) { // param = 2nd val in 2D || returns 1 if good
      int ab = 0;
      int counter = 0;
      for (ab; ab <= 7; ab++) {
        if (  (key->board[ab][PARAMETER] == 1)  ) {
        counter++;
        }
      } return counter; // if one, it is okay; if > 1, not okay
    }
    

我不太确定问题出在哪里。它似乎不喜欢我的malloc,但我立即初始化它。我尝试过更改数组大小、更改循环边界、calloc() 而不是malloc() 等等。任何帮助表示赞赏!


整个程序:

#include <stdio.h>
#include <stdlib.h>

struct chessboard {
  int board[8][8];
};

void print_board(struct chessboard* key) {
  for(int a = 0; a <= 7; a++) {
    for(int b = 0; b <= 7; b++) {
      if (key->board[a][b]==0) {
    printf("[x]");
      } else {
    printf(" q ");
      }
    } 
    printf("\n");
  } 
  puts("\n");
}

int recur(struct chessboard* key, int DEPTH, int WIDTH) {

  int depth = DEPTH;
  int width = WIDTH;

  /* set a piece as a queen */
  key->board[depth][width] = 1;

  /* check for conflicts */
  int horizontal(int column) { // param = first val in 2D || returns 1 if good
    int a = 0;
    int counter = 0;
    for(;a <= 7; a++) {
      if (  key->board[column][a] == 1  ) {
    counter++;
      }
    }
    return counter; // if one, it it is okay; if > 1, not okay
  }

  int vertical(int row) { // param = 2nd val in 2D || returns 1 if good
    int ab = 0;
    int counter = 0;
    for(;ab <= 7; ab++) {
      if (  (key->board[ab][row] == 1)  ) {
    counter++;
      }
    } 
    return counter; // if one, it is okay; if > 1, not okay
  }

  int diagnols(int column, int row) { // returns 0 if good

    int counter = 0;

    // first check diagnol up and to the left
    int abc = column-1;
    int bcd = row-1;
    while (  bcd>=0 && abc>=0 && abc<=7 && bcd<=7  ) {
      if (key->board[abc][bcd] == 1) {
    counter++;
      } abc--;
      bcd--;
    }

    // checking diagnol up and to the right
    abc = column-1;
    bcd = row+1;
    while (  abc>=0 && bcd>=0 && abc<=7 && bcd<=7  ) {
      if (key->board[abc][bcd] == 1) {
    counter++;
      } 
      abc--;
      bcd++;
    }

    // checkign diagnol down and to the left
    abc = column+1;
    bcd = row-1;
    while (  (abc>=0)&&(bcd>=0) && (abc<=7)&&(bcd<=7)  ) {
      if (key->board[abc][bcd] == 1) {
    counter++;
      } 
      abc++;
      bcd--;
    }

    // checking diagnol down and to the right
    abc = column+1;
    bcd = row+1;
    while (  abc>=0 && bcd>=0 && abc<=7 && bcd<=7  ) {
      if (key->board[abc][bcd] == 1) {
    counter++;
      } 
      abc++;
      bcd++;
    }

    return counter;
  }
  int check(int param1, int param2) { // if okay returns 2 
    int h = horizontal(param1);
    int v = vertical(param2);
    int d = diagnols(param1, param2);
    int total = 0;
    total = (h + v + d); // if okay, equals 2
    return total;
  }
  print_board(key); // shows process, can be annoying


  /* choose what to do next 
  * option puts() statements are there for debugging
  */
  int result = check(depth,width);

  if ( result == 2 && depth != 7 ) { // worked, go deeper
    // go to next row, try first column
    puts("Option 1\n");
    recur(key, (depth + 1), 0);
  } 

  else if (  result == 2 && depth==7  ) { // done
    puts("Option 2\n");
    return 1;
  } 

  else if (  result != 2 && width<7  ) { // didn't work, try next column
    // go to next column, same row
    puts("Option 3\n");
    key->board[depth][width] = 0;
    recur(key, depth, (width+1));
  } 

  else if (  result !=2 && width == 7  ) { // didn't work AND no more column space
    puts("Option 1\n");
    key->board[depth][width] = 0;  // set this queen to zero
    for (int e = 0;e<=7;e++){ // find queen in previous row, set it to zero and recur
    if (key->board[(depth-1)][e] == 1) {
        key->board[(depth-1)][e] = 0;
        recur(key, (depth-1), (e+1)); // could go out of bounds
    }
    }
  }
  return 0;
}

int main() {
  struct chessboard* master = malloc(sizeof(struct chessboard));

  /* zero out board */
  for(int one = 0; one <= 7; one++) {
    for(int two = 0; two <= 7; two++) {
      master->board[one][two] = 0;
    }
  }

  /* run */
  int result = 0;
  result = recur(master, 0, 0);

  /* finish */
  printf("Result was: %i\n", result);
  free(master);
  return 0;

}

【问题讨论】:

  • 这是一个奇怪的棋盘大小;其中大部分是 8x8 正方形。
  • @JonathanLeffler 棋盘空间从一开始,而不是零。
  • 但是你的数组是int board[7][7];,所以有效的数组索引是0..6!
  • @d0rmLife 您误解了如何声明数组。 int a[7] 声明一个数组,其索引为 06,这意味着您已经创建了一个 7x7 板。
  • 在不相关的注释中,像int one = 0; 这样的行,您声明一个变量只是为了将其用作无意义的循环计数器常量,这是非常非常糟糕的做法,只会让您的代码难以读。在 for 循环中使用常量 0 和 7。

标签: c valgrind


【解决方案1】:

问题的原始版本

由于某种原因,您使用的是 7x7 棋盘而不是普通的 8x8 棋盘:

struct chessboard {
  int board[7][7];
};

此结构可以正确索引,每个维度的索引值为 0..6。您可以摆脱(但它只是“摆脱”)一些(并且只有“一些”)索引为 7 的访问,但它们严格调用未定义的行为,这意味着“任何事情都可能发生”,包括“它几乎按预期工作'。

diagnols() 第 95 行的代码中,您有:

while (((abc >= 0) && (bcd >= 0) && (abc <= 7) && (bcd <= 7)))
{
    if (key->board[abc][bcd] == 1)
        counter++;
    ...
}

现在,如果任一索引等于 7,则您可能正在访问越界内存。


问题的修正版

好吧,既然你的棋盘是 8x8,而不是 7x7,那就更有意义了。为棋盘分配 256 字节 (8 * 8 * sizeof(int)) 的内存也很有意义。

当您有数组时,编写数组边界的标准惯用数组不是:

/* zero out board */
int one = 0;
int two = 0;
for (one; one <= 7; one++) {
  for (two; two <= 7; two++) {
    master->board[one][two] = 0;
  }
}

但是

for (int i = 0; i < 8; i++)
{
    for (int j = 0; j < 8; j++)
        master->board[i][j] = 0;
}

惯用位是for (int index = 0; index &lt; bound; index++)的使用,而不是变量名的选择(尽管ijonetwo更传统),或者大括号的位置或数量(两者都完全主观)。预先声明索引然后使用for (index = 0; index &lt; bound; index++) 没有问题,特别是如果您的编译器不支持 C99。

第 1 组问题发生在:

while (  ((abc>=0)&&(bcd>=0) && (abc<=7)&&(bcd<=7))  ) {
  if (key->board[abc][bcd] == 1) {
    counter++;
  }

这可能意味着abcbcd 之一未正确初始化,因此您正在根据未初始化的变量进行条件计算。另一种是key-&gt;board[abc][bcd]没有正确初始化,说明初始化循环错误。

另外,while 循环中的条件应该类似于for 循环中的条件:

while (abc >= 0 && bcd >= 0 && abc < 8 and bcd < 8)

这些不对称的界限(大于或等于下限——通常为零——并且小于上限)通常被认为是更好的样式。除此之外,您可以使用enum { SIDE = 8 };,然后在任何地方引用SIZE 而不是7(或8)。

对于第 2 组错误,您没有向我们展示函数 vertical 中的代码,因此我们无法判断您做错了什么,除非您的数组索引看起来太大。


继续批评

现在有一些额外的代码需要审查...

int vertical(int PARAMETER) { // param = 2nd val in 2D || returns 1 if good
  int ab = 0;
  int counter = 0;
  for (ab; ab <= 7; ab++) {
    if (  (key->board[ab][PARAMETER] == 1)  ) {
    counter++;
    }
  } return counter; // if one, it is okay; if > 1, not okay
}

通常,C 编码器使用 ALL_CAPS_NAMES 来表示宏值 #define SIZE 8enum 常量 enum { SIZE = 8 };。如果其他人要阅读您的代码,他们会期望的,因此我建议遵循该样式指南。另外,选择更有意义的名字; int vertical(int column) 会更明智,不是吗?或者int vertical(int col),因为另一个维度是row,然后你有两个三个字母的名字。你有没有考虑过使用assert from &lt;assert.h&gt;

int vertical(int column)
{
    int counter = 0;
    assert(column >= 0 && column < 8);
    for (int row = 0; row < 8; row++)
    {
        if (key->board[row][column] == 1)
           counter++;
    }
    return counter;
}

如果棋盘中的唯一值是 0 和 1,那么您甚至不需要条件;你可以简单地写:

int vertical(int column)
{
    int counter = 0;
    assert(column >= 0 && column < 8);
    for (int row = 0; row < 8; row++)
        counter += key->board[row][column];
    return counter;
}

目前,我能想到的在vertical()(任何版本)中发生越界内存访问的唯一方法是因为column 参数为8。断言将诊断是否这就是问题所在。

我猜函数diagnols() 是确定同一对角线上是否有两个皇后?你看过哈利波特系列电影吗?记住对角巷——对角线。

你显示的代码是:

  int diagnols(int PARAMETER_ONE, int PARAMETER_TWO) { // returns 0 if good
    int abc = 0;
    int bcd = 0;
    int counter = 0;

我觉得参数名应该是rowcol;您正在从给定位置 [row][col] 向上和向下搜索。

    // first check diagnol up and to the left
    abc = PARAMETER_ONE-1;
    bcd = PARAMETER_TWO-1;

考虑到这里的分配,上面将abcbcd 初始化为0 是多余的。您可能应该断言参数都在 0..7 范围内。

    while ( bcd>=0 && abc>=0 && abc<=7 && bcd<=7  ) {
      if (key->board[abc][bcd] == 1) {
        counter++;
      } abc--;
      bcd--;
    }

请不要将右大括号和另一个语句放在同一行。当然,关于不对称范围等的其他 cmets 仍然适用,但我不会重复它们。还有关于 0、1 和简单求和的观察。

    // checking diagnol up and to the right
    abc = PARAMETER_ONE-1;
    bcd = PARAMETER_TWO+1;
    while ( abc>=0 && bcd>=0  && abc<=7 && bcd<=7  ) {
      if (key->board[abc][bcd] == 1) {
        counter++;
      } abc--;
      bcd++;
    }

    // checkign diagnol down and to the left
    abc = PARAMETER_ONE+1;
    bcd = PARAMETER_TWO-1;
    while ( abc>=0 && bcd>=0 && abc<=7 && bcd<=7  ) {
      if (key->board[abc][bcd] == 1) {
        counter++;
      } abc++;
      bcd--;
    }

    // checking diagnol down and to the right
    abc = PARAMETER_ONE+1;
    bcd = PARAMETER_TWO+1;
    while ( abc>=0 && bcd>=0 && abc<=7 && bcd<=7  ) {
      if (key->board[abc][bcd] == 1) {
        counter++;
      } abc++;
      bcd++;
    }

    return counter;
  }

四个代码块非常相似。事实上,我认为您可以创建一个简单的函数来进行通用搜索。

int count_collisions(int row, int col, int row_inc, int col_inc)
{
    int count = 0;
    assert(row >= 0 && row < 8);
    assert(col >= 0 && col < 8);
    assert(row_inc >= -1 && row_inc <= +1);  // Not good for jumps in draughts/checkers
    assert(col_inc >= -1 && row_inc <= +1);
    assert(col_inc !=  0 || row_inc !=  0);  // Both zero is gives an infinite loop
    while (row >= 0 && row < 8 && col >= 0 && col < 8)
    {
        count += key->board[row][col];
        col += col_inc;
        row += row_inc;
    }
    return count;
}

int diagonals(int row, int col)
{
    int num = 0;
    num += count_collisions(row, col, +1, +1);
    num += count_collisions(row, col, +1, -1);
    num += count_collisions(row, col, -1, +1);
    num += count_collisions(row, col, -1, -1);
    return num;
}

int vertical(int col)
{
    return count_collisions(0, col, +1, 0);
}

int horizontal(int row)
{
    return count_collisions(row, 0, 0, +1);
}

Kernighan 和 Plauger 的优秀(但略显过时且已绝版)书“[The Elements of Programming Style][1]”中的一句格言适用:

  • 子例程调用允许我们总结参数列表中的不规则性 [...]
  • [t]子程序本身总结了代码的规律[...]

我认为count_collisions() 例程和使用它的函数合理地体现了这一点。

(另见:DRY if statements。)


我可以看到当前diagnols() 中存在未初始化变量的唯一方法是,如果一个或另一个参数值未初始化。确定那里发生了什么需要仔细检查调用 diagnols() 的代码。


包含完整代码

基本的编译非常干净。我没有提到我在编译时实际使用的一些选项,所以我收到了 4 个根本不是灾难性的警告:

$ gcc -O3 -g -std=c99 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition 8q.c -o 8q  
8q.c:8:6: warning: no previous prototype for ‘print_board’ [-Wmissing-prototypes]
8q.c:22:5: warning: no previous prototype for ‘recur’ [-Wmissing-prototypes]
8q.c:150:5: warning: function declaration isn’t a prototype [-Wstrict-prototypes]
8q.c: In function ‘main’:
8q.c:150:5: warning: old-style function definition [-Wold-style-definition]
$

我收到了一封来自valgrind 的投诉,最后阅读。我在没有-O3 的情况下重新编译并从valgrind 获得了更好的诊断:

然后,我为row &gt;= 0 &amp;&amp; row &lt; 8 或等效范围内的行和列的函数参数添加了断言,并触发了其中一个断言:

Assertion failed: (WIDTH >= 0 && WIDTH < 8), function recur, file 8q.c, line 30.
==23867== 
==23867== Process terminating with default action of signal 6 (SIGABRT)
==23867==    at 0x2C582A: __kill (in /usr/lib/system/libsystem_kernel.dylib)
==23867==    by 0x1A85DD: __assert_rtn (in /usr/lib/system/libsystem_c.dylib)
==23867==    by 0x100001747: recur (8q.c:30)
==23867==    by 0x1000018D8: recur (8q.c:152)
==23867==    by 0x10000182F: recur (8q.c:143)
==23867==    by 0x10000182F: recur (8q.c:143)
==23867==    by 0x10000182F: recur (8q.c:143)
==23867==    by 0x10000182F: recur (8q.c:143)
==23867==    by 0x10000182F: recur (8q.c:143)
==23867==    by 0x10000182F: recur (8q.c:143)
==23867==    by 0x10000182F: recur (8q.c:143)
==23867==    by 0x1000017BB: recur (8q.c:131)
==23867== 

所以,在最后,您(可能)使用 WIDTH 为 8 调用 recur()(而不是负值 - 您可以将断言拆分为两个单独的条件以确保)。您的recur() 函数中有关于越界的cmets:

    recur(key, (depth-1), (e+1)); // could go out of bounds

不要!这不安全。确保你在通行证上避开麻烦。 assert 是一个简单但有时(实际上,经常)有效的工具。

我很自信地说,但当我想得更多时,信心就会减弱:

实际上导致问题的那一行可能是这一行,虽然那应该是写而不是读:

/* set a piece as a queen */
key->board[depth][width] = 1;

您还没有检查depth 和(在这种情况下)width 的有效性。

【讨论】:

  • 将结构设置为 [8][8] 并不能修复它。事实上,我就是这样拥有它的——我写了我之前使用的(错误的)结构!我的 Valgrind 报告来自一个带有 [8][8] 数组的程序。
  • 所以,您希望我们推测您的实际源代码与您向我们展示的内容有何不同,以便我们可以猜测您实际上做错了什么,而不是您向我们展示的内容? 请!!!
  • 你给人留下了很好的印象,在我严重的错字之后提供了如此彻底的回应。 +1 仅此而已。 开始编码... 我知道有些东西未初始化,但我看不到它在哪里/是什么。我认为这不是我的int 计数器,因为我已经很烦人地清楚它们的值是什么。但是,我也看不出数组声明/指针传递有什么问题。我也添加了vertical(..) 函数。 编辑 我还添加了完整的diagnols() 函数。我没有看到任何未初始化的值;一定是数组?
  • 我添加了更多分析...数组初始化循环看起来不错,所以我认为diagnols() 函数的参数肯定有其他问题 - 我们只会发现完整的代码。您正在使用哪些编译选项?我推荐gcc -O3 -std=c99 -Wall -Wextra -Werror;使用-O3,编译器可能会警告您未初始化的变量。应仔细检查由此产生的任何警告并修复原因。
  • 好的!我正在使用~$ gcc -g -Wall prog.c。我尝试了您的调用建议,它选择了一些不必要的代码行(正如您已经指出的那样)。我清理了那些。此外,分段错误已经消失(耶),但 Valgrind 仍然抱怨(嘘)。我已经发布了整个程序——如果你看一看,我将非常感激。您的指导确实很有启发性。
【解决方案2】:

发生第一个错误是因为您 malloced 了一些东西(大概是一个 chessboard,它似乎被称为 key)并且在尝试读取它们之前未能设置一些结构的内部成员。也就是说,您尝试读取key-&gt;board[abc][bcd] 而不先分配到该位置。

您尚未针对其他错误发布相应的代码。另请注意,发布所有代码很有帮助,因为程序的行很少存在于真空中。

【讨论】:

  • 该程序相当大,所以我尽量节俭。我添加了更多细节。如您所见,我将所有元素设置为零。但是,这不会导致错误。您发现代码缺少哪个错误?
猜你喜欢
  • 1970-01-01
  • 2021-05-27
  • 1970-01-01
  • 2011-04-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-24
相关资源
最近更新 更多