C语言 将包含大小的二维数组传递给函数

ix0qys7i  于 2023-10-16  发布在  其他
关注(0)|答案(4)|浏览(113)

我想把一个二维数组传递给一个函数,但是,我想用 arbitrary size来做这件事。
首先,请看以下代码:

#define MAX_ROWS 8
#define MAX_SIZE 32

static void foo(char array[][MAX_SIZE], size_t max_rows, size_t max_size) {
    for (size_t i = 0; i < max_rows; i++) {
        printf("%s\n", array[i]);
    }
}

int main(void) {
    char array[MAX_ROWS][MAX_SIZE];
    
    for (size_t i = 0; i < MAX_ROWS; i++) {
        snprintf(array[i], MAX_SIZE, "Row: %d", i);
    }
    
    foo(array, MAX_ROWS, MAX_SIZE);
    
    return 0;
}

***但有一句我不喜欢:

static void foo(char array[][MAX_SIZE], size_t max_rows, size_t max_size)

我不喜欢它,因为我不仅需要指定两次最大大小,我还需要硬编码第二维的最大大小,以便在编译时可用。

**B.**另一种方法是创建可变长度数组(VLA)。为此,我们需要改变参数的顺序,使size_t max_size出现在数组之前:

static void foo(size_t max_rows, size_t max_size, char array[][max_size])
foo(MAX_ROWS, MAX_SIZE, array);

一方面我喜欢这个解决办法,但另一方面我又不喜欢。它是C99标准的一部分,但不受MSVC支持。所以,我根本不能使用它。
此外,我不确定这种使用是否会由于安全原因而被劝阻,即使大小-由于使用宏常量-仍然 * 事实上 * 硬编码,因此实际上不是可变的。

**C.**据我所知,char array[MAX_ROWS][MAX_SIZE]在内部只是一个大小为max_rows * max_size * sizeof(char)的缓冲区。因此,我在考虑以下解决方案,但结果是Segmentation Fault

static void foo(void *array0, size_t max_rows, size_t max_size) {
    size_t total_size = max_rows * max_size * sizeof(char);
    
    char *array = malloc(total_size);
    memcpy(array, array0, total_size);

    for (size_t i = 0; i < max_rows; i++) {
        printf("%s\n", array[i]);
    }

    free(array);
}
wkyowqbh

wkyowqbh1#

你可以在char *上使用指针运算,这是像qsort这样的函数在不知道传递数组的具体类型时的操作方式:

#include <stdio.h>

static void foo(void *array0, size_t max_rows, size_t max_size)
{
    const char *ptr = array0;

    for (size_t i = 0; i < max_rows; i++) {
        printf("%s\n", ptr);
        ptr += max_size;
    }
}

int main(void)
{
    enum {MAX_ROWS = 3, MAX_SIZE = 4};
    char array[MAX_ROWS][MAX_SIZE] = {"abc", "cde", "fgh"};

    foo(array, MAX_ROWS, MAX_SIZE);
    return 0;
}

输出量:

abc
cde
fgh

关于C-FAQ
然而,必须指出,以这种方式“手工”执行多维数组下标的程序并不严格符合ANSI C标准;根据官方的解释,访问(&array[0][0])[x]的行为并没有定义为x >= NCOLUMNS。
因此,这个答案(以及@0_使用行优先顺序提供的另一个答案)并不严格符合标准。

cngwdvgl

cngwdvgl2#

备注:

  • C没有像FORTRAN那样的多维数组。只有数组。
  • 数组是一个地址的名称,并有一个大小,如int array[40]
  • 数组的名称指向它的地址。
  • 大小是元素个数与每个元素大小的乘积
  • 但是C可以有任何数组,所以数组可以是数组的数组,数组的数组的数组,等等。
  • 数组元素从左起一行接一行地分配在存储器中。

示例

考虑这段简单的代码

#include <stdio.h>

int main(void)
{
    union
    {
        char i3D[2][2][2];
        char i1D[8];
    } view = {0, 1, 2, 3, 4, 5, 6, 7};

    printf(
        "size of `char i3D[2][2][2]` is %u\n",
        sizeof(view.i3D));
    printf(
        "size of `char[2][2][2]` is %u\n",
        sizeof(char[2][2][2]));
    printf("size of `char` is %u\n", sizeof(char));

    printf(
        "first element is %d at 0x%p, last is %d at 0x%p\n",
        view.i3D[0][0][0], &view.i3D[0][0][0], view.i3D[1][1][1],
        &view.i3D[1][1][1]);
    char offset = &view.i3D[1][1][1] - &view.i3D[0][0][0];
    printf(
        "offset of the last element from the start is %d * "
        "sizeof(char) = %d\n",
        offset, offset * sizeof(view.i3D[0][0][0]));

    return 0;
}

这表明在测试运行中

size of `char i3D[2][2][2]` is 8
size of `char[2][2][2]` is 8
size of `char` is 1
first element is 0 at 0x012FFD44, last is 7 at 0x012FFD4B
offset of the last element from the start is 7 * sizeof(char) = 7

如果我们检查这个地址的内存

0x012FFD40  cc cc cc cc 00 01 02 03 04 05 06 07  ÌÌÌÌ........
0x012FFD4C  cc cc cc cc 87 56 cb 02 74 fd 2f 01  ÌÌÌÌ.VË.tý/.
0x012FFD58  63 22 da 00 01 00 00 00 f0 5c 62 01  c"Ú.....ð\b.
0x012FFD64  08 63 62 01 01 00 00 00 f0 5c 62 01  .cb.....ð\b.
0x012FFD70  08 63 62 01 d0 fd 2f 01 b7 20 da 00  .cb.Ðý/.· Ú.

我们在运行时看到数组数据在预期的地址

返回原始代码

这段代码试图将一个字符串数组传递给foo()函数。
例如,这是main的通用原型,适用于每个C程序:

int main (int argc, char** argv)

它做同样的事情。一直都很有效。类型不同,因为foo()声明的是

static void foo(char array[][MAX_SIZE], size_t max_rows, size_t max_size)

但是在foo()内部,只能通过调用printf()来访问行,

for (size_t i = 0; i < max_rows; i++)
        printf("%s\n", array[i]);

%s用于空终止字符串,因此foo()中的char_array非常类似于每个C程序中的argv

char block[10][20]char** line

block中,我们有一个200字节的连续数据块。line是一个指针。

  • block[0]char[20],最多可以有19个char加上终止NULL
  • block[0]block[9],我们可以有10个这样的字符串。
  • line也可以看作是一个数组。这是C * 数组到指针衰减 * 的事情。
  • *linechar*。它是*运算符的结果。
  • line[0]*line相同。C * 指针运算:line[0] = *(line + 0)
  • **line是单个char。第一个字符串的第一个字母,如block中的block[0][0]

但是我们如何知道block中有多少行和多少行呢?line中有多少个指针?

我们没有。我们需要保留其他地方保存的维度,或者我们计算:这就是为什么我们需要strlen()来随时计算字符串大小的原因。类似的东西肯定可以用于line,在line的末尾使用NULL指针

这就是在main中使用argc的原因:在argv数组中保存指针的数量

封装及示例

我将使用这种封装思想编写一个示例,将数据封装在struct中,并编写最少的函数来操作它。

使用char**

就像系统为main构建一个数组一样,我们将在line中创建argv数组:

typedef struct
    {
        size_t incr;   // increment size
        size_t limit;  // actual allocated size
        size_t size;   // size in use
        char** line;   // the lines
    } Block;

每个块都是空的,大小= limit。如果需要,可以根据一组incr指针调整大小。原因是:重新分配存储器在时间方面可能是昂贵的,因为可能需要复制整个阵列。

Block函数

Block* create_blk(size_t size, size_t increment);
Block* delete_blk(Block* block_to_go);
int    resize_blk(Block* block_to_go);
int    show_blk(Block* block, const char* msg);

最低检测要求。函数名是自解释的。

  • show_blk()接受opional消息
  • resize()仅用于扩展。如果需要,缩小尺寸是rivial
    一个可能的实现,一个使用的例子是在这篇文章的结尾

测试用函数helper

Block* load_file(const char* file);

这个函数只是将一个文件加载到一个新的Block中,并返回Block地址。测试功能的简单方法。

main()进行测试

int main(int argc, char** argv)
{
    char msg[80] = {0};
    if (argc < 2) usage();
    Block* test = load_file(argv[1]);
    if (test == NULL) return -1;
    sprintf(
        msg, "\n\n==> Loading \"%s\" into memory", argv[1]);
    show_blk(test, msg);
    qsort(test->line, test->size, sizeof(void*), cmp_line);
    sprintf(msg, "\n\n==> \"%s\" after sort in-memory", argv[1]);
    show_blk(test, msg);
    test = delete_blk(test);
    return 0;
};

此程序要求在命令行上显示文件名。然后

  • 创建一个Block文件中的所有行
  • 显示结构在内存中的内容
  • 调用qsort()对块内的所有行进行排序
  • 显示了struct中的命令行
  • 删除所有内容

p.exe stuff.h输出

下面是使用文件stuff.h进行测试的输出

loading "stuff.h" into memory
Block extended for a total of 20 pointers

==> Loading "stuff.h" into memory
Status: 18 of 20 lines. [Incr. is 16]:
   1    #pragma once
   2    #include <stdio.h>
   3    #include <stdlib.h>
   4    #include <string.h>
   5
   6    typedef struct
   7    {
   8        size_t incr;   // increment size
   9        size_t limit;  // actual allocated size
  10        size_t size;   // size in use
  11        char** line;   // the lines
  12
  13    } Block;
  14
  15    Block* create_blk(size_t size, size_t increment);
  16    Block* delete_blk(Block* block_to_go);
  17    int    resize_blk(Block* block_to_go);
  18    int    show_blk(Block* block, const char* msg);

==> "stuff.h" after sort in-memory
Status: 18 of 20 lines. [Incr. is 16]:
   1
   2
   3
   4        char** line;   // the lines
   5        size_t incr;   // increment size
   6        size_t limit;  // actual allocated size
   7        size_t size;   // size in use
   8    #include <stdio.h>
   9    #include <stdlib.h>
  10    #include <string.h>
  11    #pragma once
  12    Block* create_blk(size_t size, size_t increment);
  13    Block* delete_blk(Block* block_to_go);
  14    int    resize_blk(Block* block_to_go);
  15    int    show_blk(Block* block, const char* msg);
  16    typedef struct
  17    {
  18    } Block;

stuff.h stuff.c main.c完整代码【本次测试】

stuff.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct
{
    size_t incr;   // increment size
    size_t limit;  // actual allocated size
    size_t size;   // size in use
    char** line;   // the lines

} Block;

Block* create_blk(size_t size, size_t increment);
Block* delete_blk(Block* block_to_go);
int    resize_blk(Block* block_to_go);
int    show_blk(Block* block, const char* msg);

stuff.c

#include "stuff.h"

Block* create_blk(size_t size, size_t increment)
{
    Block* nb = (Block*)malloc(sizeof(Block));
    if (nb == NULL) return NULL;
    nb->incr  = increment;
    nb->limit = size;
    nb->size  = 0;
    nb->line  = (char**)malloc(sizeof(char*) * size);
    return nb;
}

Block* delete_blk(Block* blk)
{
    if (blk == NULL) return NULL;
    for (size_t i = 0; i < blk->size; i += 1)
        free(blk->line[i]);  // free lines
    free(blk->line);         // free block
    free(blk);               // free struct
    return NULL;
}

int resize_blk(Block* nb)
{
    const size_t new_sz = nb->limit + nb->incr;
    char*        new_block =
        realloc(nb->line, (new_sz * sizeof(char*)));
    if (new_block == NULL)
    {
        fprintf(
            stderr,
            "\tCould not extend block to %zd "
            "lines\n",
            new_sz);
        return -1;
    }
    nb->limit = new_sz;
    nb->line  = (char**)new_block;
    return 0;
}   // resize_blk()
 
int show_blk(Block* bl, const char* msg)
{
    if (msg != NULL) printf("%s\n", msg);
    if (bl == NULL)
    {
        printf("Status: not allocated\n");
        return -1;
    }
    printf(
        "Status: %zd of %zd lines. [Incr. is %zd]:\n",
        bl->size, bl->limit, bl->incr);
    for (unsigned i = 0; i < bl->size; i += 1)
        printf("%4d\t%s", 1 + i, bl->line[i]);
    return 0;
}

main.c

#include "stuff.h"

int    cmp_line(const void*, const void*);
Block* load_file(const char*);
void   usage();

int main(int argc, char** argv)
{
    char msg[80] = {0};
    if (argc < 2) usage();
    Block* test = load_file(argv[1]);
    if (test == NULL) return -1;
    sprintf(
        msg, "\n\n==> Loading \"%s\" into memory", argv[1]);
    show_blk(test, msg);
    qsort(test->line, test->size, sizeof(void*), cmp_line);
    sprintf(msg, "\n\n==> \"%s\" after sort in-memory", argv[1]);
    show_blk(test, msg);
    test = delete_blk(test);
    return 0;
};

int cmp_line(const void* one, const void* other)
{
    return strcmp(
        *((const char**)one), *((const char**)other));
}

Block* load_file(const char* f_name)
{
    if (f_name == NULL) return NULL;
    fprintf(stderr, "loading \"%s\" into memory\n", f_name);
    FILE* F = fopen(f_name, "r");
    if (F == NULL) return NULL;
    // file is open
    Block* nb = create_blk(4, 16);  // block size is 8
    char   line[200];
    char*  p = &line[0];
    p        = fgets(p, sizeof(line), F);
    while (p != NULL)
    {
        // is block full?
        if (nb->size >= nb->limit)
        {
            resize_blk(nb);
            printf(
                "Block extended for a total of %zd "
                "pointers\n",
                nb->limit);
        }
        // now copy the line
        nb->line[nb->size] = (char*)malloc(1 + strlen(p));
        strcpy(nb->line[nb->size], p);
        nb->size += 1;
        // read next line
        p = fgets(p, sizeof(line), F);
    };  // while()
    fclose(F);
    return nb;
}

void usage()
{
    fprintf(stderr, "Use: program file_to_load\n");
    exit(EXIT_FAILURE);
}

没有真正测试过,但它显示了一种写这种东西的方法。由于代码在stuff.c文件中,因此可以在几分钟内在任何程序中使用。只包含stuff.h

qnzebej0

qnzebej03#

在我的情况下,最好的方法-正如@tstanisl所建议的那样-是更改编译器以使用VLA,确切地说:为了使用VMT,一个可变修改的类型。这不仅是C99标准的一部分,而且在即将到来的C23中是强制性的(参见here),所以我错误地认为这是不鼓励的。
在我的例子中,我将编译器改为Clang。MSVC中的Clang支持可以安装在
New Project > Installed > Templates > Visual C++ > Cross Platform
然后在
Project Properties > Configuration Properties > General > Platform Toolset

x8goxv8g

x8goxv8g4#

void foo(void *array, size_t rows, size_t cols) {
    char *arr = array;
    for (size_t i = 0; i < rows; i++) {
        printf("%s\n", arr + i * cols);
    }
}

int main(void) {
    char array[10][100];
    
    for (size_t i = 0; i < sizeof(array) / sizeof(array[0]); i++) {
        snprintf(array[i], sizeof(array[0]) / sizeof(array[0][0]), "Row: %d", i);
    }
    
    foo(array, sizeof(array) / sizeof(array[0]), sizeof(array[0]) / sizeof(array[0][0]));
    
    return 0;
}

相关问题