如何使用malloc为结构体的二维数组分配内存?(总线错误:10)

3
我将尝试实现数独求解器。为此,我使用下面展示的结构体来表示数独板上的一个单元格。然后,我声明了一个9x9的这些结构体数组来表示整个数独板。
单元格结构体:
struct cell{
     char value;
     unsigned int possible;
};

然后,我声明了一个结构体数组如下:
struct cell board[9][9];

我的问题是,当我尝试向数组中输入一个值(例如,board[2][2].value = getchar()),有时它可以正常工作,而有时候会出现以下错误:

Bus error: 10

我不太确定这是什么意思... "总线错误: 10"与分段错误有何不同? 我正在使用gcc并在vim中进行编辑。 我的感觉是,我需要动态分配此数组的内存。 现在,我了解如何使用malloc为二维数组分配内存,类似于以下内容:
int ** Array;  
Array = (int**) malloc(x_size*sizeof(int*));  
for (int i = 0; i < x_size; i++)  
    Array[i] = (int*) malloc(y_size*sizeof(int)); 

但是我在实现一个二维结构体数组的内存分配部分时遇到了问题。

是否应该像这样实现?

struct cell** board;
board = (struct cell**) malloc(x_size*sizeof(struct cell**));
for(int i=0; i< x_size; i++)
    board[i] = (struct cell*) malloc(y_size*sizeof(struct cell));

我担心这个 " sizeof(struct cell) " 没有正确分配应该分配的内存量。任何帮助都将不胜感激!我对 C 语言还比较新(C++ 是我的母语),我已经使用了嵌入式 C 很长时间,但我正在尝试更好地掌握整个语言。
如果能提供详细的解释就更好了!感谢!

编辑
好的,感谢大家提供的好建议,我还没有实现任何动态内存分配,但是,按照要求,这是产生总线错误的代码:
 /* only code being used in solver.h*/
 29 /* structure to describe a cell */
 30 struct cell{
 31     int value;
 32     unsigned int possible;
 33 };



   /*solver.c*/
 4 #include <ctype.h>
 5 #include <stdio.h>
 6 #include "solver.h"
 7 
 8 
 9 struct cell board [9][9];
 10 
 11 
 12 int main(){
 13     initialize_board();
 14     print_board();
 15     setup_board();
 16     print_board();
 17 return 0;
 18 }
 19 
 20 void print_board(){
 21     int i=0, j=0;
 22     for(i = 0; i<9; i++){
 23         for(j = 0; j<9; j++)
 24             printf(" %d",board[i][j].value);
 25         printf("\n");
 26     }
 27 }
 28 
 29 void initialize_board(){
 30     int i = 0, j = 0;
 31 
 32     for(i = 0; i<9; i++)
 33         for(j = 0; j<9; j++){
 34             (board[i][j]).value = 0;
 35             (board[i][j]).possible = 0x1FF;
 36         }
 37 }
 38 
 39 void setup_board(){
 40     int row=0, col=0, val = 0;
 41     char another = 'Y';
 42 
 43     printf("Board Initial Setup.\nEnter the row and column number of the value to be entered into the board.");
 44     printf("\nRow and Column indexes start at one, from top left corner.");
 45     while(another == 'Y'){
 46         printf("\nRow: ");
 47         row = getchar();
 48         printf("Column: ");
 49         getchar();
 50         col = getchar();
 51         printf("Value: ");
 52         getchar();
 53         (board[row-1][[col-1]).value = getchar();
 54         printf("Enter another value? (y/n): ");
 55         getchar();
 56         another = toupper(getchar());
 57         getchar();
 58     }
 59 }

如您所见,我已将value的数据类型更改为int以匹配getchar()的返回类型。但是我的代码仍然产生奇怪的运行时错误/结果。例如,在setup_board的while循环的第一次迭代中,我可以输入:行:1,列:1,值:5,然后当我输入'n'退出时,应该在左上角打印出带有数字5的棋盘,但事实并非如此。打印的矩阵仍处于initialize_board()被调用之后的状态。
输出:
Board Initial Setup.
Enter the row and column number of the value to be entered into the board.
Row and Column indexes start at one, from top left corner.
Row: 1
Column: 1
Value: 4
Enter another value? (y/n): n
 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0

此外,如果我输入其他矩阵坐标,就会出现总线错误: 输出:
Board Initial Setup.
Enter the row and column number of the value to be entered into the board.
Row and Column indexes start at one, from top left corner.
Row: 5
Column: 5
Value: 5
Bus error: 10

任何关于如何清理这个难看的double getchar()的建议都将不胜感激。
谢谢大家!
第二次编辑
问题出在那些getchar()上...我没有意识到它们返回一个整数ASCII码来代表数字,而不是实际的数字本身。这是我为了解决问题所做的:
 47     while(another == 'Y'){
 48         valid=0;
 49         while(!valid){
 50             printf("\nRow: ");
 51             row = getchar() - '0';  /* convert ASCII character code to actual integer value */
 52             getchar();              /*added to remove extra newline character from stdin */
 53             printf("Column: ");
 54             col = getchar() - '0';
 55             getchar();              /*remove \n */
 56             printf("Value: ");
 57             val = getchar() - '0';
 58             getchar();              /*remove \n */
 59             if(val >9 || val<1 || col>9 ||col<1 || row>9 || row<1)
 61                 printf("\nInvalid input, all values must be between 1 and 9, inclusive");
 62             else
 63                 valid = 1;  
 64         }
 65         board[row-1][col-1].value = val;
 66         printf("Enter another value? (y/n): ");
 67         another = toupper(getchar());
 68         getchar();                  /*remove \n */
 69     }

感谢大家的帮助和意见,我将实施你们提出的许多建议,并且仅从这个问题中就学到了很多!
第三次修改
最后一个问题!
尽管我的原始问题已解决,但是否有人对动态分配内存来实现矩阵有强烈的意见或理由呢?
我想,既然它现在正常工作,我就把它留下来。但是,由于矩阵相当大,动态分配内存会更好吗?

1
在程序设计中,应避免强制类型转换malloc的返回类型。 - keety
你能展示一下出错的代码吗?你第一个例子中的数组声明应该是没有问题的,我也声明了这样一个数组并且填充它没有任何问题。 - Aaron Dufour
@keety 为什么我应该避免将 malloc 的返回类型进行类型转换?我认为这通常是必须的。由于 malloc 返回 void*,难道我不需要将其转换为实际存储数据的类型吗? - MRT89
你使用的是什么机器/CPU?我对"总线错误:10"这个问题很感兴趣,想要查一下。 - vhallac
@vhallac ESP损坏了?我的堆栈指针怎么会被损坏? - MRT89
显示剩余5条评论
5个回答

2

getchar() 函数返回一个 int 类型的值。也许这个返回值超出了 char 类型的范围。 查看 getchar() 函数


@MRT89 如果你试图将一个int复制到char中,可能会导致总线错误。 - keety
嗯...好吧,我只输入'1-9'给getchar()。我应该转换为char类型吗?val = (char) getchar();? - MRT89
或者,在我声明结构体的头文件中,我可以将value更改为int或short? - MRT89
1
将int赋值给char只会丢弃最高有效字节。通常这不是问题,但如果启用了最高警告级别,它会指出可能存在数据丢失的情况:http://stackoverflow.com/questions/10503434/why-no-overflow-warning-when-converting-int-to-char - Bruno Kim

2

首先,关于习语的一些说明。在C语言中有一个用于malloc数组的习语:

Type *ptr;
ptr = malloc (n * sizeof(*ptr));

sizeof运算符不仅可以接收类型,还可以接收该类型的变量。请注意ptr前面的星号,这意味着我们正在分配大小为Type而不是Type*的内容。无需强制转换返回值,因为void*指针可以分配给任何指针。这样,您可以定义一个宏来分配任何数组,如下所示:

#define ALLOC(p, n) p = malloc(n * sizeof(*p))

另外,在分配二维或多维矩阵时,通常会一次性获取所需的所有内存,具体如下:

Type **matrix;
matrix = malloc(row * sizeof(*matrix));
matrix[0] = malloc(row * col * sizeof(*matrix[0]))
for (i=0; i < row; i++){
    matrix[i] = matrix[0] + i*col;
}

这样我们只进行了两次分配,一次是为了获取行头指针,另一次是为了获取所有所需的内存。之后,我们使所有行头指针指向矩阵中正确的位置,这样我们就可以使用常规惯用语,如matrix[i][j]。有些人也喜欢分配单个向量,并使用matrix[i*col + j]进行访问,但我觉得第一种方法更清晰易懂。

最后,虽然这不是一个确定的问题,但我发现将结构体定义为类型更容易理解,这样就不需要一直提醒它确实是一个结构体。

typedef struct Cell Cell;
struct Cell{
    ...
};

Cell board[9][9];

最后,我测试了您的静态单元板,没有发现奇怪的总线错误。这可能是由于字符填充引起的,但我认为这不太可能。它可能是由于getchar引起的问题吗?它会捕捉换行符和空格。


我认为这一定与getchar()有关。我现在会发布产生错误的代码。我最初在main()中声明了board矩阵,然后将其传递给操作它的函数。我发现这有点低效,因为几乎每个我从main调用的函数都需要将board矩阵传递给它。我改变了我的代码,使静态矩阵在全局声明。 - MRT89
我知道void*可以分配给任何指针,但是我认为类型转换更好?我喜欢解引用指针的malloc,并将使用它。此外,我真的很喜欢typedef,这非常有意义,可能会大大减少我的代码大小。感谢您的建议!我现在会尝试按照您的建议实现内存分配。 - MRT89
那个分配宏也很有趣,我也会尝试一下。谢谢! - MRT89

2

getchar函数返回代表一个字符的代码,你需要添加代码将其转换为数字。例如,考虑以下代码:

printf("\nRow: ");
row = getchar();
printf("Column: ");
getchar();

当用户输入"hehe"而不是程序所期望的11时,会发生什么?程序将获取he的ASCII码,将其分配给rowcol,然后访问超出范围的数组元素。
实际上,当正常输入时也会发生同样的情况!用户输入1,程序获取ASCII码(49)并执行一些内存溢出操作。
board[49-1][49-1].value = 53;

为了解决这个问题,将字符代码转换为数字:
if (row >= '1' && row <= '9')
    row -= '0'; // a common C hack for converting a character-code to a number
else
    printf("Bad input"); // not a proper error handling, just an example

col -= '0'; // 1 line for brevity; you must do the same code as above

value = getchar();
value -= '0'; // same as above

board[row-1][col-1].value = value;

是的!我一直在想为什么调试时得到的是'49'而不是1。如果我只使用一个scanf函数来获取所有3个值,我是否仍然需要转换ASCII代码? - MRT89
你是对的。问题就出在那些该死的getchar()上。我已经修复了,可以看到更新后带有新编辑的问题。谢谢! - MRT89

1
除了问题Q已经被回答之外,关于您对malloc等的评论。 malloc返回指向大小为size的对象的未初始化新分配空间的指针。
您不会收到例如int的空间,而是大小为N的空间。这就是为什么您可以取一个char并读取int中的字节。
现在有一些点是主观的,也许有点偏离SO,但试试看。

强制转换malloc是多余的,只会使代码混乱。这就好像用水倒满一个瓶子,然后再用这个瓶子倒满另一个瓶子一样。分配的内存不会“变成”它被强制转换的类型。它只是一块内存而已。总之,从某种程度上来说,这是在逆向思考。

int *指向虚拟内存中的特定部分,并根据int大小以块为单位处理该内存。根据字节顺序,在具有4字节int的特定系统中,它将是LSB先或后在4字节序列中。


当涉及到宏时,我不建议使用宏来执行malloc等函数。这会使代码难以阅读,并且像grep等文件操作变得无用/更难。

当遇到宏时,必须检查该宏的作用。这会增加一层容易出错的代码等。当你在6个月后或其他人阅读代码时,你将不得不检查该宏的实际作用。如果你读取malloc,你就知道它的作用,但是当你读取MYMALLOC时,你将一无所知。

这是基本的构建块,最好保持原样。考虑到痛苦,没有收益。

有时候会遇到以下代码:

BLAH(k, v);
while (--i)
     BOFF(mak, VED(uu));
if (FOO != k)
      BAR;

现在,要解密,你必须阅读宏,这通常是模糊不清的定义,很快就会变成一团糟。你知道原语和标准函数。不要隐藏正在发生的事情。

永远不要使用宏改变程序的控制流程。


当涉及到typedef时,我通常讨厌它们。我从不使用typedef struct。

比如说,struct foo bar;BLAH bar;。在后者中,bar可以是任何东西。一个函数、一个无符号字符、一个结构体、一个原始指针等等。随着代码的增长,这成为了另一件需要跟踪的事情。前者使用struct简洁而短小,任何阅读它的人都知道它是一个结构体,而且它不是一个指针。

在大型项目中,它往往更多地使代码变得晦涩难懂,而不是更加清晰明了。 不要隐藏正在发生的事情。

typedef对于函数指针等可以有用,但是同样需要谨慎使用函数指针。


同样适用于#define。请谨慎使用。

有趣。我觉得我看过一些将malloc强制转换类型的代码,但是既然你说的是真的,那么这是不必要的。我尽量避免使用预处理器指令,如宏或定义,但我认为当宏的作用显而易见时,它们可以很有用,例如像MAX(A,B)(#define MAX(a,b) (((a)>(b))?(a):(b)))这样的东西。如果我确实使用了宏或#define,则确保它们的名称非常描述它所代表的含义/功能。typedef也是如此。typedef struct cell cell非常清晰,但你说的很有道理。谢谢! - MRT89
+1 对不同意见的好论据。在C语言中,宏是一个很棒的工具,当你需要避免重复时使用,但是由于不同类型(例如变长参数和我示例中的ALLOC),函数并不合适。当然,它们应该轻松对待,主要是因为函数更安全。隐藏信息是从面向对象编程思想中获益最大之一:专注于契约而非实现。typedef对于在具有相同类型的变量之间提供区分(以及隐藏struct)是有用的。然而我也同意malloc的强制转换和从不改变宏中的程序流程。 - Bruno Kim

0

我不知道为什么你会得到“Bus 10:”错误,使用静态数组应该可以正常工作。

但是对于动态分配board内存,你可以使用以下代码:

cell ** board = (cell **) malloc(x_size * sizeof(cell *));

for (int i = 0; i < Size_X; i++)
{
    board[i] = (cell *) malloc (y_size * sizeof (cell));
}

你也可以将board分配为81个cell的简单数组:

cell * board = (cell *) malloc (x_size * y_size (cell));

我已经在Windows下测试了静态分配,它可以正常工作。我也使用malloc测试了上述代码,但我的编译器是设置为C++的,所以上述代码可能无法与纯C编译器完全兼容。

希望这有所帮助。


总线错误也可能意味着内存芯片出现故障,驱动器故障,驱动器已满,内存不足...但我假设您已经测试并排除了那个原因。此外,在升级出现问题的情况下,编译器也可能会变得损坏。 - George
我最近将这台机器的RAM从4GB升级到16GB。我已经测试了RAM,一切似乎都很好。我的Macbook识别了16GB的RAM,我能够运行多个虚拟机并给它们每个2-4GB的RAM...我的RAM安装可能没有像我想象的那样顺利吗? - MRT89
不要认为是内存问题,如果所有的BIOS和操作系统都正确识别了内存。你可以尝试一些工具来测试内存,但这很不可能。总线错误也可能意味着你正在尝试读取或写入只读内存。考虑到这一点——这可能是由于你正在尝试将int写入char引起的。int类型可能比char类型多占用了几个字节,因此额外的字节可能会溢出到某些只读或不可用于程序的内存部分。尝试将char变量更改为int并进行测试。 - George
所以我已经将变量更改为int,但仍然出现错误。然而,问题在于我的getchar()。我刚刚检查了一下,它们没有返回我输入的整数... - MRT89

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接