C - 使用 malloc 获取 char** 的地址

标签 c malloc

我正在尝试用 C 创建一个 tic tac toe 游戏,并且我有一个创建游戏板的函数:

void createBoard(char*** board, int size)
{   
    int i, j;
    *(board) = malloc(size * sizeof( char* ));
    for(i = 0; i < size; i++)
    {
        *(board)[i] = malloc(size * sizeof(char));
    }

    printf("Default board:\n");

    for(i = 0; i < size; i++)
    {
        for(j = 0; j < size; j++)
        {
            *(board)[i][j] = '-';
            printf("%c", *(board)[i][j]);
            printf("  ");
            if((j + 1) % size  == 0)
                printf("\n");
        }
    }
}

在主函数中,我向该函数传递一个字符**的地址:

int main()
{
    int size;
    char** board = NULL;
    char userInp;

    sizeOfBoard(&size);

    createBoard(&board, size);
}

我使用 *(board) 访问指针的值(最初为 NULL),然后将 malloc 返回的地址分配给它,但我不知道为什么它不起作用。有人可以帮我解决这个问题吗?

最佳答案

不要那样做。相反,创建一个描述板和板状态的结构,并使用它。在该结构中,使用一维数组表示棋盘状态:

typedef struct {
    int   rows;
    int   cols;
    char *cell;
}  board;
#define  BOARD_INIT  { 0, 0, NULL }

#define  CELL(b, r, c) ((b).cell[(c) + (r)*(b).cols])

要创建面板,您可以使用例如

int  create_board(board *b, const int rows, const int cols)
{
    const size_t  cells = (size_t)rows * (size_t)cols;
    const size_t  bytes = sizeof (board) + cells * sizeof (b->cell[0]);

    if (!b) {
        /* No board specified. */
        errno = EINVAL;
        return -1;
    }

    b->rows = 0;
    b->cols = 0;
    b->cell = NULL;

    if (rows < 1 || cols < 1) {
        /* Invalid size. */
        errno = EINVAL;
        return -1;
    }
    if ((size_t)(cells / (size_t)rows) != (size_t)cols ||
        (size_t)((bytes - sizeof (board)) / cells) != sizeof (b->cell[0])) {
        /* Board is too large. */
        errno = ENOMEM;
        return -1;
    }

    b->cell = malloc(bytes);
    if (!b->cell) {
        /* Board was too large. */
        errno = ENOMEM;
        return -1;
    }

    b->rows = rows;
    b->cols = cols;

    return 0;
}

void  free_board(board *b)
{
    if (b) {
        free(b->cell);
        b->rows = 0;
        b->cols = 0;
        b->cell = NULL;
    }
}

您可以使用CELL(board, row, column) 访问宏,或以下安全访问器函数:

char  get_cell(board *b, const int r, const int c, const char outside)
{
    if (!b || r < 0 || c < 0 || r >= b.rows || c >= b.cols)
        return outside;
    else
        return b->cell[r*b->cols + c];
}

void set_cell(board *b, const int r, const int c, const char value)
{
    if (b && r >= 0 && c >= 0 && r < b.rows && c < b.cols)
        b->cell[r*b->cols + c] = value;
}

get_cell()set_cell() 都不会尝试访问实际板之外的内容。 (get_cell() 的第四个参数 outside 定义了当您尝试访问无效板或板外时的结果值。)

<小时/>

说实话,我确实更喜欢使用 C99 灵活数组成员作为单元数组。在这种情况下,结构类似于

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <errno.h>

typedef struct {
    int            rows;
    int            cols;
    unsigned char  cell[];
} board;

board 类型有点类似于 FILE 流句柄:您永远不会静态声明它们(board b;FILE f;),但使用指向一的指针(board *bFILE *f)。要创建新板,您可以使用例如

board *create_board(const int rows, const int cols)
{
    board        *b;
    const size_t  cells = (size_t)rows * (size_t)cols;
    const size_t  bytes = cells * sizeof (b->cell[0]);
    const size_t  total = bytes + sizeof (board);

    /* Check for invalid size */
    if (rows < 1 || cols < 1) {
        errno = EINVAL;
        return NULL;
    }

    /* Check for size overflow */
    if ((size_t)(cells / (size_t)rows) != (size_t)cols ||
        (size_t)(bytes / cells) != sizeof (b->cell[0]) ||
        total <= bytes) {
        errno = ENOMEM;
        return NULL;
    }

    /* Allocate memory for the whole board */
    b = malloc(total);
    if (!b) {
        /* Failed. Too large. */
        errno = ENOMEM;
        return NULL;
    }

    /* Optional: Clear the entire structure. */
    memset(b, 0, total);

    /* Initialize the board fields. */
    b->rows = rows;
    b->cols = cols;

    return b;
}

void free_board(board *b)
{
    if (b) {
        b->rows = 0;
        b->cols = 0;
        free(b);
    }
}

在 C99 中,我们可以将访问器函数声明为静态内联,这意味着它们仅在同一翻译单元中可见(因此您可以在声明 的头文件中声明它们>board 结构),并且也是 C 编译器应该内联这些的提示。基本上,静态内联函数应该与预处理器宏一样快,而且还提供类型安全。

static inline int get_cell(board *b, const int row, const int col,
                           const int outside)
{
    if (!b || row < 0 || col < 0 || row >= b->rows || col >= b->cols)
        return outside;
    else
        return b->cell[row * b->cols + col];
}

static inline int  set_cell(board *b, const int row, const int col,
                            const int value, const int outside)
{
    if (b && row >= 0 && col >= 0 && row < b->rows && col < b->cols)
        return b->cell[row * b->cols + col] = value;
    else
        return outside;
}

要打印板(使用 |+- 作为单元格之间的线),您可以使用例如

static int board_char(board *b, const int row, const int col)
{
    if (b && row >= 0 && col >= 0 &&
        row < b->rows && col < b->cols) {
        const int  value = b->cell[row * b->cols + col];
        if (isprint(value))
            return value;
        else
            return ' ';
    } else
        return ' ';
}

void print_board(FILE *out, board *b)
{
    if (out && b && b->rows > 0 && b->cols > 0) {
        const int  lastrow = b->rows - 1;
        const int  lastcol = b->cols - 1;
        int        r, c;

        for (r = 0; r < lastrow; r++) {
            for (c = 0; c < lastcol; c++) {
                fputc(board_char(b, r, c), out);
                fputc('|', out);
            }
            fputc(board_char(b, r, lastcol), out);
            fputc('\n', out);

            for (c = 0; c < lastcol; c++) {
                fputc('-', out);
                fputc('+', out);
            }
            fputc('-', out);
            fputc('\n', out);
        }

        for (c = 0; c < lastcol; c++) {
            fputc(board_char(b, lastrow, c), out);
            fputc('|', out);
        }
        fputc(board_char(b, lastrow, lastcol), out);
        fputc('\n', out);
    }
}

其中board_char()返回与引用的单元格相对应的描述性字符。上面的版本检查数组中的值是否是可打印字符,如果是,则返回它;否则,包括棋盘外的字符,它返回一个空格。您可以使用带有 case 语句的 switch (value) { ... } 来代替 if (isprint(value))您为每个单元格使用的值。

(例如,如果零表示未使用/空闲,1表示第一个玩家(例如X),2表示第二个玩家(例如O),您可以使用3代表“被阻止”,标记一些单元格不可用,使游戏变得更有趣。)

您可以使用广泛的 Curses 库(例如 ncursesw)使您的游戏在终端中具有交互性,并使用漂亮的方框绘制字符来绘制游戏板。例如,

┌───┬───┬───┬───┬───┐
│ X │   │   │   │   │
├───┼───┼───┼───┼───┤
│   │   │ O │   │   │
├───┼───┼───┼───┼───┤
│   │   │   │   │   │
├───┼───┼───┼───┼───┤
│   │   │   │   │   │
├───┼───┼───┼───┼───┤
│   │   │   │   │   │
└───┴───┴───┴───┴───┘

您可以使用本地化和宽字符支持将上述类型的板打印到终端,而无需使用 Curses 库,但不幸的是在 Windows 中会出现问题。 (到目前为止,Microsoft 仍在致力于为终端/控制台应用程序提供适当的 Unicode 支持,而不需要使用 Microsoft 特定的 C 扩展。)

关于C - 使用 malloc 获取 char** 的地址,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51423531/

相关文章:

c - 将数据存储在数组中

objective-c - 创建的对象数量

c - Linux C 读取一个目录

c++ - 如何使在.h中创建的变量成为程序的全局范围

使用 calloc 创建字符串

c - 如何从二进制字符串转换为二进制无符号字符

c - 如何使用 malloc 在 ANSI - C 中声明动态整数数组并将输入整数放入其中?

c - malloc() 中的段错误?

c - 在 C 中声明字符串而不给出大小

c - 为什么我的 malloc 在 lubuntu 上的 netbeans 中导致找不到源错误 (malloc.c)?