[英]Passing an array of 'typedef struct' to a function
我有以下情况:
文件Ac:
typedef struct element
{
uint16_t value_raw;
float value_scaled;
char *desc;
} element;
element sv[REG_READ_COUNT];
文件啊:
typedef struct element element;
文件Bc:
#include "A.h"
void dostuff (element sv[]) { }
在编译时我得到“错误:数组类型具有不完整的元素类型”为Bc中的函数参数定义
这样做的正确方法是什么? 如何将'element'类型的数组传递给函数?
在Bc
, element
是一个不完整的类型(它没有在Ah
定义,只在Ac
定义)。 C不允许具有不完整元素类型的数组声明符(如您所发现的)。 以下是C99草案的相关文字:
6.7.5.2数组声明符
约束
- 除了可选的类型限定符和关键字
static
,[
和]
可以分隔表达式或*
。 如果它们分隔表达式(指定数组的大小),则表达式应具有整数类型。 如果表达式是常量表达式,则其值应大于零。 元素类型不应是不完整或函数类型 。 可选的类型限定符和关键字static
只出现在具有数组类型的函数参数的声明中,然后仅出现在最外层的数组类型派生中。
强调我的。 这适用于所有数组声明符,无论它们出现在何处:在变量声明,typedef,函数参数列表等中。
要修复代码,请将完整的结构定义放在Ah
。 或者,如果dostuff
实际上不需要使用元素(例如,只是将“数组”传递给其他函数),则可以使用void dostuff(element *sv)
。
重现错误的最小代码。
struct element;
void dostuff (struct element sv[]) { }
使用coliru测试clang
和gcc
: http ://coliru.stacked-crooked.com/a/e5e314deef461290
结果:GCC和clang总是抱怨类型数组不完整类型的参数,而不是指向不完整类型的指针。
相关标准报价:
6.7.6.3函数声明符(包括原型)
[...]
4调整后,作为该函数定义一部分的函数声明符中参数类型列表中的参数不应具有不完整类型。
[...]
7参数声明为''类型数组''应调整为''限定指向类型'',其中类型限定符(如果有)是在数组类型派生的[
和]
中指定的那些。 如果关键字static
也出现在数组类型派生的[
和]
中,则对于每次对函数的调用,相应实际参数的值应提供对数组的第一个元素的访问,其中至少有指定的元素数量。按大小表达式。
好吧,到这里看起来像不完整类型的数组对于参数类型来说完全没问题,即使在定义中也是如此。
6.2.5类型
[...]
20可以从对象和函数类型构造任意数量的派生类型,如下所示:
- 数组类型描述了具有特定成员对象类型的连续分配的非空对象集,称为元素类型。 只要指定了数组类型,元素类型就应该是完整的。 数组类型的特征在于它们的元素类型和数组中的元素数。 数组类型据说是从其元素类型派生的,如果它的元素类型是T,则数组类型有时称为''T'数组。 从元素类型构造数组类型称为“数组类型派生”。
对于每种情况,上面的引用明确禁止使用具有不完整类型的数组语法。
结论:所有这些编译器似乎都是正确的,即使这种限制似乎是不必要的。
无论如何,正确的过程不是对类型进行前向声明,而是将类型本身的声明放入头文件中,除非它是一个不透明的类型。
在这种情况下,您必须直接使用参数类型的指针语法。
您的编译错误由Deduplicator的答案描述。
您可以通过编写element *sv
来解决此问题。 但是, Bc
只能看到定义typedef struct element element;
。 它无法看出构成element
。
如果“真实”版本dostuff
做任何事情与sv
需要知道什么实际结构包含,那么你需要定义移动struct element
从Ac
到Ah
。
作为提供一种方法来执行OP所需但是假设他需要数据隐藏的第二个答案,我提出了基于我的第一个答案构建的代码,并提供对一个C
文件中的元素类型的泛型访问,并仅提供不透明的数据类型在头文件中。 请注意,为了明确指出什么是指针,我使用了element *
但是它们可以全部被ELEM_HANDLE
替换,我将其定义为标题中的类型。 ELEM_HANDLE
抽象出我们正在处理元素指针的事实。 由于我们使用opaque类型,因此我们提供可以调用的方法(在element.h
定义)以处理我们的opaque类型。
element.h展开:
#include <stdint.h>
typedef struct element element;
typedef element *ELEM_HANDLE;
extern element *element_new();
extern void element_delete(element *elem);
extern void element_set_value_raw(element *elem, uint16_t value_raw);
extern uint16_t element_get_value_raw(element *elem);
extern void element_set_value_scaled(element *elem, float value_scaled);
extern float element_get_value_scaled(element *elem);
extern void element_set_desc(element *elem, char *desc);
extern char *element_get_desc(element *elem);
element.c:
#include <stdint.h>
#include <stdlib.h>
typedef struct element
{
uint16_t value_raw;
float value_scaled;
char *desc;
} element;
element *element_new()
{
return calloc(1, sizeof(element));
}
void element_delete(element *elem)
{
free(elem);
}
void element_set_value_raw(element *elem, uint16_t value_raw)
{
elem->value_raw = value_raw;
}
uint16_t element_get_value_raw(element *elem)
{
return elem->value_raw;
}
void element_set_value_scaled(element *elem, float value_scaled)
{
elem->value_scaled = value_scaled;
}
float element_get_value_scaled(element *elem)
{
return elem->value_scaled;
}
void element_set_desc(element *elem, char *desc)
{
elem->desc = desc;
}
char *element_get_desc(element *elem)
{
return elem->desc;
}
testelem.c:
#include <stdio.h>
#include "element.h"
#define REG_READ_COUNT 2
void dostuff(element *sv[], int arrLen)
{
int index;
element *curelem;
uint16_t raw;
float scaled;
char *desc;
for (index = 0; index < arrLen ; index++){
curelem = sv[index];
raw = element_get_value_raw(curelem);
scaled = element_get_value_scaled(curelem);
desc = element_get_desc(curelem);
/* Do more interesting stuff here */
printf("%s, %d, %.4f\n", desc, raw, scaled);
}
}
int main()
{
unsigned int index;
element *sv[REG_READ_COUNT]; /* array of element pointers*/
char desc1[] = "The answer to everything";
char desc2[] = "OtherStuff";
/* Initialize an array of pointers to element items */
for (index = 0; index < sizeof(sv) / sizeof(element *); index++)
sv[index] = element_new();
element_set_value_raw(sv[0], 42);
element_set_value_scaled(sv[0], 6.66f);
element_set_desc(sv[0], desc1);
element_set_value_raw(sv[1], 123);
element_set_value_scaled(sv[1], 456.7f);
element_set_desc(sv[1], desc2);
dostuff(sv, REG_READ_COUNT);
/* free the array of pointers to element items*/
for (index = 0; index < sizeof(sv) / sizeof(element *); index++)
element_delete(sv[index]);
return 0;
}
请注意,我采用自由将数组长度传递给元素指针数组旁边的dostuff
。 这为dostuff提供了足够的信息来确定数组中有多少元素。 这应该在C89或更高版本和C ++编译器上正确编译(并运行)(假设您将.c
文件重命名为.cpp
)。
我提出这个答案,因为使用前向声明和不透明类型是创建了多少“C”语言共享对象。 此机制允许将元素源编译为独立的库或共享对象,并在不知道element
数据类型的外观的情况下使用。 本质上,我们在使用我们和库的模块之间提供接口契约。 如果我们修改element.cpp
结构元素的内部结构,那么使用它的模块将不需要重新编译(只需重新链接)。 如果我们修改接口(合同),则需要重建使用该库的客户端代码。
因此,最后,前向引用(opaque类型)可用于隐藏C
数据类型内部并提供一个抽象层。 共享对象( .so
文件)经常使用这种类型的机制来构建可由C
程序使用的复杂库。
因为Ah只定义了一个opaque类型的typedef struct element element
Bc不可能知道元素的组成甚至确定它的大小。 所以它无法创建这些结构的数组。 如果您希望此代码工作,则必须将Ac中的整个typedef移动到Ah。 如果你这样做,那么没有信息隐藏,并且通过标题可以获得完整的结构。
另外,您可以创建一个指向结构的指针数组(即使它可能不完整)并将其传递给您的函数,但您将无法直接访问任何结构成员变量。
在指向这些类型的指针数组中使用不透明数据类型的示例:
typedef struct element element;
#define REG_READ_COUNT 100
void dostuff (element *sv[])
{
sv++; /* get next pointer to element */
};
int main()
{
element *sv[REG_READ_COUNT]; /* array of pointers to element */
dostuff(sv);
}
这个代码很好,直到它需要任何需要实际类型大小的东西。 我们甚至无法将数据成员初始化为任何内容,而无需额外的粘合代码(另一个模块)实际上可以访问完整的元素类型。
您可以拥有指针数组(甚至是不完整类型)的原因是因为指针是C中的基本类型。它既不是不完整类型也不是函数类型。 指针具有固定大小,编译器可以使用它来生成指针数组。
6.7.5.2数组声明符
约束
除了可选的类型限定符和关键字static之外,[和]可以分隔表达式或*。 如果它们分隔表达式(指定数组的大小),则表达式应具有整数类型。 如果表达式是常量表达式,则其值应大于零。 元素类型不应是不完整或函数类型 。 可选的类型限定符和关键字static只出现在具有数组类型的函数参数的声明中,然后仅出现在最外层的数组类型派生中。
因为指针不是不完整的类型或函数类型,所以即使它们指向不完整的类型,也可以创建它们的数组。 指向不完整类型的指针不会使指针不完整。 你无法取消引用它,并希望直接对它做任何有用的事情。 我直接说,因为在数据隐藏技术和不透明指针中你可以提供间接机制来处理不透明指针数据。
下面是一个代码示例,该代码无法以与OP类似的方式进行编译。 我们认为可以传递指向不完整类型的指针(函数参数),但它们仍然不能用作函数内的数组:
typedef struct element element;
#define REG_READ_COUNT 100
void dostuff (element *sv) /* Completely legal but useless if you intend to use it as an array */
{
sv++; /* This should fail - as we are doing array arithmetic on
* an incomplete type. Can't find the starting point of the next
* array element without knowing the size of the object */
};
int main()
{
element sv[REG_READ_COUNT]; /* array of elements will also fail - size of object unknown */
dostuff(sv);
}
这几乎与前一个相同。 在这一个中,我们有一个指向不完整类型的指针sv
作为函数参数(这来自nneonneo答案)。 这完全合法,因为它只是一个指针。 然而,尝试对其进行数组运算(在body函数中使用++
)将失败,因为它需要知道元素的大小并且它是未知的。 ++和 - 或索引数组是未定义的行为(大多数符合标准的编译器都会抛出错误)。 ISO / IEC 9899:TC2说:
6.3.2其他操作数
6.3.2.1左值,数组和函数指示符
...
2除非它是sizeof运算符的操作数,一元&运算符,++运算符, - 运算符或者左运算符。 运算符或赋值运算符,没有数组类型的左值将转换为存储在指定对象中的值(并且不再是左值)。 如果左值具有限定类型,则该值具有左值类型的非限定版本; 否则,该值具有左值的类型。 如果左值具有不完整类型且没有数组类型,则行为未定义
有关不透明类型的更多信息,请点击此处
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.