gpt4 book ai didi

Passing two-dimensional array with including sizes to function(将包含大小的二维数组传递给函数)

转载 作者:bug小助手 更新时间:2023-10-27 19:56:49 25 4
gpt4 key购买 nike



I want to pass a two-dimensional array to a function, however, I want to do that with arbitrary size.

我想要将一个二维数组传递给一个函数,但是,我想用任意大小来做这件事。


First, please have a look to following code:

首先,请看一下以下代码:


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

A. This code works as expected. But there is one line I don't like:

答:这段代码的工作情况与预期相符。但有一句话我不喜欢:


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

I don't like it because not only I need to specify the maximum size two times, I need to hardcode the maximum size for the 2nd dimension to make it available during compile time.

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


B. An alternative approach would be making an variable length array (VLA). For that we would need to change the order of the parameters so the size_t max_size appears before the array:

B.另一种方法是制作可变长度阵列(VLA)。为此,我们需要更改参数的顺序,以便大小_t最大大小显示在数组之前:


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

I like this solution on one hand, but I don't like it on the other hand. It is part of the C99 standard but not supported by MSVC. Therefore, I simply can't use it.

一方面我喜欢这个解决方案,但另一方面我不喜欢它。它是C99标准的一部分,但不受MSVC支持。因此,我根本不能使用它。


Also I am not sure if this kind of use would be discouraged due to security reasons, even if the size is - due to the use of the macro constants - still de facto hardcoded and therefore actually not variable.

此外,我不确定这种使用是否会因为安全原因而不被鼓励,即使大小--由于使用了宏常量--实际上仍然是硬编码的,因此实际上不是可变的。


C. From what I understand, char array[MAX_ROWS][MAX_SIZE] is internally nothing more then a buffer with a size of max_rows * max_size * sizeof(char). So I was thinking about following solution, but this results in a Segmentation Fault:

C.据我所知,char数组[MAX_ROWS][MAX_SIZE]在内部只不过是一个大小为max_row*max_Size*sizeof(Char)的缓冲区。因此,我正在考虑以下解决方案,但这会导致分段故障:


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

更多回答

printf("%s\n", array[i]); you can't print a char with %s

Printf(“%S\n”,数组[i]);不能打印%S的字符

I want to print the sequence of characters until \0 termination. In both variant A and B it works. You're right that the error occurs at printf, but that is not the real reason as I don't want to only print one character.

我想打印直到0终止的字符序列。在变体A和B中,它都是有效的。您说得对,错误发生在printf上,但这不是真正的原因,因为我不想只打印一个字符。

Sometimes you can replace a VLA with a flexible array member. Have you considered using a real C compiler (MinGW or clang) on Windows?

有时,您可以用灵活的阵列成员替换VLA。你有没有考虑过在Windows上使用真正的C编译器(MinGW或clang)?

Well, I only do C, so that might be a thing to think about, but I've not yet researched enough about that because I primarily do C only for Windows based programs whereas I use other languages on Linux. I will have a look to flexible array members.

嗯,我只做C语言,所以这可能是一件值得考虑的事情,但我还没有对此进行足够的研究,因为我主要只为基于Windows的程序做C语言,而我在Linux上使用其他语言。我将了解一下灵活的数组成员。

@Tintenfisch, VLA objects are discouraged, but VLA types are very encouraged. They are mandatory again in upcoming C23. Read the post describing common misconceptions of VLAs and how they should be used properly.

@Tintisch,不鼓励使用VLA对象,但非常鼓励使用VLA类型。在即将到来的C23中,它们再次成为必填项。请阅读描述对VLA的常见误解以及如何正确使用它们的帖子。

优秀答案推荐

You can use pointer arithmetic over a char *, this is how functions like qsort do it when they don't know the concrete type of the passed array:

您可以在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;
}

Output:

产出:


abc
cde
fgh

Quote from the C-FAQ:

引用C-FAQ中的话:



It must be noted, however, that a program which performs
multidimensional array subscripting ``by hand'' in this way is not in
strict conformance with the ANSI C Standard; according to an official
interpretation, the behavior of accessing (&array[0][0])[x] is not
defined for x >= NCOLUMNS.



Therefore, this answer (and the other answer provided by @0___________ using row-major order) are not strictly conforming with the standard.

因此,这一答案(以及@0_提供的答案不严格符合标准。



Notes:

备注:



  • C has no multi-dimensional arrays like FORTRAN. Just arrays.

  • array is a name for an address and has a size, like in int array[40].

    • the name of the array points to its address.

    • the size is the product of the number of elements by the size of each one



  • but C can have arrays of anything, so an array can be an array of arrays, an array of arrays of arrays, and so on.

  • array elements are allocated in memory line after line, from left.


EXAMPLE


Consider this simple code

请考虑以下简单的代码


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

That shows in a test run

这一点在试运行中得到了体现


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

If we inspect memory at this address

如果我们检查此地址的内存


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.Ðý/.· Ú.

we see the array data at the expected address at runtime

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


Back to the original code


The code are trying to pass an array of strings to a foo() function.

代码试图将字符串数组传递给foo()函数。


As an example, this is the common prototype of main, for every C program:

例如,这是每个C程序的Main的公共原型:


    int main (int argc, char** argv)

And it does the same thing. And works, ever. The types are different, since foo() is declaring

它做的也是同样的事情。而且永远奏效。类型是不同的,因为foo()声明


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

But inside foo() the rows are only accessed by a call to printf() as

但在foo()中,行只能通过调用printf()来访问,因为


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

And %s is for a null-terminated string, so char_array in foo() is very much like argv in every C program

而%S表示以NULL结尾的字符串,因此foo()中的char_array非常类似于每个C程序中的argv


From a char block[10][20] to a char** line


In block we have a continuous block of data with 200 bytes. line is a single pointer.

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



  • block[0] is char[20] and can have a string of up to 19 char plus the terminating NULL.

  • from block[0] to block[9] we can have 10 of these strings.

  • line can also be seen as an array. It is C array to pointer decay thing.

  • *line is char*. It is the result of the * operator.

  • line[0] is the same as *line. It is C *pointer arithmetic: line[0] = *(line + 0).

  • **line is a single char. The first letter of the first string, like block[0][0] in block


But how do we know how many rows and lines are in block? And how many pointers in line?


We do not. We need to keep the dimensions saved elsewhere, or we count: this is why we need strlen() to compute a string size anytime we need. Something similar can for sure be used for line here, using a NULL pointer at the end of line

我们没有。我们需要将维度保存在其他地方,否则就需要计数:这就是为什么我们需要strlen()来在需要的任何时候计算字符串大小。类似的东西肯定可以用于这里的line,在行尾使用空指针


This is the reason for argc in main: keep count of the number of pointers in the argv array

这就是在main中使用argc的原因:计算argv数组中的指针数


Encapsulation and an example


I will write an example using this idea of encapsulation, enclosing the data in a struct and writing a minimum of functions to manipulate it.

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


Using char**


Just as the system builds one for main we will create the argv array in line:

就像系统为Main构建一个一样,我们将创建一个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;

Each block is created empty, with a size = limit. If needed a resize is made in terms of a set of incr pointers. The reason for this: reallocate memory can be expensive in terms of time, since the whole array may need to be copied.

每个块都是空的,并带有size = limit。如果需要的话,可以根据一组增量指针来调整大小。原因是:重新分配内存在时间方面可能很昂贵,因为整个数组可能需要复制。


functions for 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);

The mininum for testing. Function names are self explanatory.

测试的最低要求。函数名称是不言而喻的。



  • show_blk() accepts an opional message

  • resize() is implemented only for expansion. Reduce size is rivial if needed


A possible implementation an example of use is at the end of this post

本文末尾提供了一个可能的实现和使用示例


function helpers for testing


        Block* load_file(const char* file);

This function just loads a file into a new Block and returns the Block address. Easy way of testing the functions.

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


main() for a test


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;
};

This program expects a file name on the command line. Then

该程序在命令行中需要一个文件名。然后



  • creates a Block with all lines in file

  • shows the contents of the struct as it is in memory

  • calls qsort() to sort all lines inside the block

  • shows the lines ordered, inside the struct

  • deletes everything


output for p.exe stuff.h


Here is the output of a test using the file 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;

Complete code for stuff.h stuff.c main.c [this test]


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

Not really tested, but it shows a way of writing this kind of stuff. As the code is in the stuff.c file it can be used in minutes in any program. Just include stuff.h.

没有经过真正的测试,但它展示了一种编写这种东西的方法。因为代码在Stuff.c文件中,所以它可以在几分钟内在任何程序中使用。只需包含Stuff.h。



The best way to go in my case - as recommended by @tstanisl - was to change the compiler in order to make use of a VLA, to be precise: In order to use a VMT, a variably-modified type. Not only is this anyway part of the C99 standard, it is mandatory in the upcoming C23 (see here), so I was wrong thinking it was discouraged.

在我的例子中,最好的方法--就像@tstanisl推荐的那样--是为了使用VLA而更改编译器,准确地说:为了使用VMT,一种可变修改的类型。这不仅是C99标准的一部分,而且在即将发布的C23标准中也是强制性的(请看这里),所以我错误地认为这是不受欢迎的。


In my case I changed the compiler to Clang. Clang support in MSVC can be installed in

在我的例子中,我将编译器更改为Clang。MSVC中的Clang支持可安装在


New Project > Installed > Templates > Visual C++ > Cross Platform

新建项目>已安装>模板>Visual C++>跨平台


and then be enabled in

然后在以下位置启用


Project Properties > Configuration Properties > General > Platform Toolset.

项目属性>配置属性>常规>平台工具集。



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;
}

更多回答

This is very helpful to learn. Which is the reason I've chosen to first learn C and then C++ because I want to understand C very deeply.

这对学习很有帮助。这就是我选择先学习C,然后学习C++的原因,因为我想非常深入地理解C。

Well, SO is not a place for opinion, but I would add that changing the compiler to use an obscure feature is almost never possible. VLA is great for students that struggle to grasp the need to know an array size at compile time. The way to go in any OO language is to encapsulate your data and size info as an object and build the code around this object. Is ok in C, Assembler. C++. You call a thing and get a pointer to some bytes. You call other thing and free this bytes.

好吧,这不是一个可以发表意见的地方,但我想补充的是,将编译器更改为使用一个晦涩难懂的功能几乎是不可能的。VLA对于努力掌握在编译时需要知道数组大小的学生来说非常有用。使用任何面向对象语言的方法都是将数据和大小信息封装为对象,并围绕该对象构建代码。用C语言编写也可以,汇编程序。C++。你调用一个东西,然后得到一个指向一些字节的指针。你调用其他东西并释放这个字节。

Isn't the VLA (better said the VMT), which is C99 standard, not simply the better solution here? Because the size is de-facto static, so known at compiler time, it is only about to pass it through the function (I currently only use C to not mix C and C++).

VLA(更好地说是VMT)是C99标准,难道不是这里更好的解决方案吗?因为它的大小实际上是静态的,所以在编译时就知道了,所以它只会通过函数传递它(我目前只使用C来避免混用C和C++)。

I see your point, but at the first line of your post: I want to pass a two-dimensional array to a function, however, I want to do that with arbitrary size. It is arbitrary or is it known at compile time? In general we write code as generic as possible. That is one reason for encapsulation. And note that VLA can work around X[N] but will not give you the value of N. It can be or end up in the standard, but it will go anywhere but students code. A framework will not return you a VLA or VLT, a network driver also will not. We will see

我明白你的意思,但在你的帖子的第一行:我想把一个二维数组传递给一个函数,然而,我想用任意大小来做这件事。它是任意的,还是在编译时就知道了?通常,我们编写尽可能通用的代码。这是封装的原因之一。请注意,VLA可以绕过X[N],但不会给出N的值。它可以是标准的,也可以是最终的,但它将适用于任何地方,除了学生代码。框架不会返回VLA或VLT,网络驱动程序也不会。我们拭目以待

And who would want a variable sized thing in the stack? And if it goes on the heap or in the stack? Who would rely on such a thing? And if it goes always in the heap then goes away the possible speed gain and comes in a simple allocation.

谁会想要一个大小可变的东西呢?如果它被放入堆中或堆栈中呢?谁会依赖这样的东西呢?如果它总是在堆中,那么就不会有可能的速度收益,而是一个简单的分配。

'arbitrary' was indeed the wrong term here or at least misleading, because more important here is that the size is known at compiler time, so I changed the headline accordingly to my code example. You say a VLA will not give me the value of N. I don't understand. What exactly is the problem with a VMT? Please have a look to the example in the proposal of C23: open-std.org/jtc1/sc22/wg14/www/docs/n2778.pdf

“任意的”在这里确实是错误的,或者至少是误导的,因为这里更重要的是大小在编译时是已知的,所以我根据我的代码示例相应地更改了标题。你说VLA不会给我N的价值。我不明白。VMT到底有什么问题?请看C23:open-std.org/jtc1/sc22/wg14/www/docs/n2778.pdf提案中的示例

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com