将一个'typedef struct'数组传递给一个函数

时间:2014-09-14 22:09:45

标签: c arrays typedef

我有以下情况:

文件A.c:

typedef struct element
{
    uint16_t value_raw;
    float value_scaled;
    char *desc;
} element;

element sv[REG_READ_COUNT];

文件A.h:

typedef struct element element;

文件B.c:

#include "A.h"
void dostuff (element sv[]) { } 

在编译时我得到 “错误:数组类型具有不完整的元素类型” 用于B.c。中的函数参数定义。

这样做的正确方法是什么? 如何将'element'类型的数组传递给函数?

5 个答案:

答案 0 :(得分:6)

B.c中,element是一种不完整的类型(A.h中未定义,仅在A.c中定义)。 C不允许具有不完整元素类型的数组声明符(正如您已发现的那样)。以下是C99草案的相关文字:

  

6.7.5.2数组声明符

     

约束

     
      
  1. 除了可选的类型限定符和关键字static之外,[]可以分隔表达式或*。如果它们分隔表达式(指定数组的大小),则表达式应具有整数类型。如果表达式是常量表达式,则其值应大于零。 元素类型不应为不完整或函数类型。可选的类型限定符和关键字static应仅出现在具有数组类型的函数参数的声明中,然后仅出现在最外层的数组类型派生中。
  2.   

强调我的。这适用于所有数组声明符,无论它们出现在何处:在变量声明,typedef,函数参数列表等中。

要修复代码,请将完整的结构定义放在A.h中。或者,如果dostuff实际上不需要使用元素(例如,只是将&#34;数组&#34;传递给其他函数),则可以使用void dostuff(element *sv)。< / p>

答案 1 :(得分:2)

重现错误的最小代码。

struct element;
void dostuff (struct element sv[]) { } 

使用coliru对clanggcc进行测试:http://coliru.stacked-crooked.com/a/e5e314deef461290
结果:GCC和clang总是抱怨类型数组不完整类型的参数,而不是指向不完整类型的指针。

相关标准引用:

  

6.7.6.3函数声明符(包括原型)

     

[...]
  4调整后,函数声明符中参数类型列表中的参数即为   该功能定义的一部分不应具有不完整的类型   [...]
  7参数声明为''类型''的数组应调整为''限定指针   type'',其中类型限定符(如果有)是在[]内指定的类型限定符。   数组类型推导。如果关键字static也出现在[]内   数组类型派生,然后对每个函数调用,对应的值   实际参数应提供对至少具有至少数量的数组的第一个元素的访问   由size表达式指定的元素。

嗯,到目前为止看起来像一个不完整类型的数组对于参数类型来说完全没问题,即使在定义中也是如此。

  

6.2.5类型

     

[...]
  20可以从对象和函数类型构造任意数量的派生类型,如   如下:

     
      
  • 数组类型描述了一个连续分配的非空对象集   特定的成员对象类型,称为元素类型。 元素类型应为   指定数组类型时完成。数组类型以其为特征   元素类型和数组中元素的数量。据说是一种数组类型   从其元素类型派生,如果其元素类型为T,则有时是数组类型   称为''T'数组'。调用元素类型的数组类型的构造   ''数组类型派生''。
  •   

以上引用明确禁止对每种情况使用不完整类型的数组语法。

结论:所有这些编译器似乎都是正确的,即使这种限制似乎是不必要的。


无论如何,正确的做法是不对类型进行前向声明,而是将类型本身的声明放入header-file中,除非它是一个不透明的类型。

在这种情况下,您必须直接使用参数类型的指针语法。

答案 2 :(得分:0)

您的编译错误由Deduplicator的答案描述。

您可以撰写element *sv来解决此问题。但是,B.c只能看到定义typedef struct element element;。它无法查看构成element的内容。

如果&#34;真实&#34;版本dostuff执行sv需要知道该结构实际​​包含的内容的任何内容,然后您需要将struct element的定义从A.c移到A.h

答案 3 :(得分:0)

作为提供一种方法来执行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程序使用的复杂库。

答案 4 :(得分:-1)

因为A.h只定义了一个opaque类型typedef struct element element,所以B.c不可能知道元素的组成,甚至不能确定它的大小。所以它无法创建这些结构的数组。如果您希望此代码有效,则必须将A.c中的整个typedef移动到A.h.如果你这样做,那么就没有信息隐藏,并且通过标题可以获得完整的结构。

此外,您可以创建一个指向结构的指针数组(即使它可能不完整)并将其传递给您的函数,但您将无法直接访问任何结构成员变量。

在指向这些类型的指针数组中使用不透明数据类型的示例:

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运算符的操作数,否则一元&amp;运算符,++       运算符, - 运算符或者左运算符。运算符或赋值运算符,       没有数组类型的左值被转换为存储在指定的值       对象(并且不再是左值)。如果左值具有限定类型,则该值具有       左值类型的不合格版本;否则,该值具有类型       左值。 如果左值具有不完整的类型且没有数组类型,则行为为       未定义

    
  

有关opaque类型的更多信息,请访问here