简体   繁体   中英

Passing an array of 'typedef struct' to a function

I have the following situation:

file Ac:

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

element sv[REG_READ_COUNT];

file Ah:

typedef struct element element;

file Bc:

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

at compile time I get "error: array type has incomplete element type" for the function argument definition in Bc

What's the right way to do this? How can I pass an array of type 'element' into a function?

In Bc , element is an incomplete type (it is not defined in Ah , only in Ac ). C disallows array declarators with incomplete element types (as you've discovered). Here's the relevant text from the C99 draft:

6.7.5.2 Array declarators

Constraints

  1. In addition to optional type qualifiers and the keyword static , the [ and ] may delimit an expression or * . If they delimit an expression (which specifies the size of an array), the expression shall have an integer type. If the expression is a constant expression, it shall have a value greater than zero. The element type shall not be an incomplete or function type . The optional type qualifiers and the keyword static shall appear only in a declaration of a function parameter with an array type, and then only in the outermost array type derivation.

Emphasis mine. This applies to all array declarators, no matter where they occur: in variable declarations, typedefs, function parameter lists, etc.

To fix your code, put the full struct definition in Ah . Or, if dostuff doesn't actually need to work with the elements (eg simply to pass the "array" to some other function), you could use void dostuff(element *sv) .

Minimal code to reproduce the error.

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

Testing on clang and gcc using coliru: http://coliru.stacked-crooked.com/a/e5e314deef461290
Result: GCC and clang always complain about arguments of type array of incomplete type, and never about pointer to incomplete type.

Relevant standard-quotes:

6.7.6.3 Function declarators (including prototypes)

[...]
4 After adjustment, the parameters in a parameter type list in a function declarator that is part of a definition of that function shall not have incomplete type.
[...]
7 A declaration of a parameter as ''array of type'' shall be adjusted to ''qualified pointer to type'', where the type qualifiers (if any) are those specified within the [ and ] of the array type derivation. If the keyword static also appears within the [ and ] of the array type derivation, then for each call to the function, the value of the corresponding actual argument shall provide access to the first element of an array with at least as many elements as specified by the size expression.

Well, up to here it looks like array of incomplete type was perfectly fine for an argument type, even in a definition.

6.2.5 Types

[...]
20 Any number of derived types can be constructed from the object and function types, as follows:

  • An array type describes a contiguously allocated nonempty set of objects with a particular member object type, called the element type. The element type shall be complete whenever the array type is specified. Array types are characterized by their element type and by the number of elements in the array. An array type is said to be derived from its element type, and if its element type is T, the array type is sometimes called ''array of T''. The construction of an array type from an element type is called ''array type derivation''.

The above quote explicitly disallows using array-syntax with an incomplete type, for every case.

Conclusion: All those compilers seem to be right, even though that restriction seems unneeded.


Anyway, the proper course is not putting a forward-declaration for the type, but the declaration for the type itself into the header-file, unless it shall be an opaque type.

In that case, you will have to use pointer-syntax for the argument-type directly.

Your compilation error is described by Deduplicator's answer.

You can work around the issue by writing element *sv . However, Bc can only see the definition typedef struct element element; . It can't see what makes up the element .

If the "real" version of dostuff does anything with sv that requires knowing what that struct actually contains, then you need to move the definition of struct element from Ac into Ah .

As a secondary answer to provide a method to do what the OP wanted but assuming he needed data hiding, I present this code that builds on my first answer and provides generic access to an element type in one C file and providing only an opaque data type in the header file. Please note that to get a point across about what are pointers I use element * however they could have all been replaced by ELEM_HANDLE that I define as a type in the header. ELEM_HANDLE abstracts away the fact that we are dealing with element pointers. Since we use an opaque type we make available methods that can be called (defined in element.h ) to work on our opaque type.

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

Note that I took liberties to pass in the array length to dostuff beside the array of element pointers. This provides dostuff with enough information to determine how many elements are in the array. This should compile (and run) properly on C89 or above and C++ compilers (provided you rename the .c files to .cpp ).

I present this answer because using forward declarations and opaque types is how many "C" language shared objects are created. This mechanism allows the element source to be compiled into an independent library or shared object and used without knowing what the element data type looks like. In essence we provide an interface contract between the modules that use us and the library. If we modify the internals of the structure element in element.cpp our modules that use it will not need to be recompiled (just re-linked). client code that uses the library would need to be rebuilt if we modify the interface(contract).

So in the end forward references (opaque types) can be used to hide C data type internals and provide a layer of abstraction. This type of mechanism is often used by shared objects ( .so files) to build complex libraries that can be used by C programs.

Because Ah only defines an opaque type typedef struct element element Bc can't possibly know the make up of element to even determine its size. So it can't create an array of those structures. If you want this code to work you would have to move the entire typedef in Ac to Ah . If you do this then there is no information hiding and the full structure is available through the header.

Additionally, You could create an array of pointers to structures (even though it may be incomplete) and pass it into your function but you wouldn't be able to access any of the structures member variables directly.

An example of using an opaque data type in arrays of pointers to those types:

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

This code is fine until it needs anything requiring the sizeof the actual type. We can't even initialize the data members to anything without extra glue code (another module) that actually does have access to the complete element type.

The reason you can have arrays of pointers (even to incomplete types) is because a pointer is a basic type in C. It is neither an incomplete type or a function type. A pointer has fixed size that the compiler can use to generate pointer arrays.

6.7.5.2 Array declarators

Constraints

In addition to optional type qualifiers and the keyword static, the [ and ] may delimit an expression or *. If they delimit an expression (which specifies the size of an array), the expression shall have an integer type. If the expression is a constant expression, it shall have a value greater than zero. The element type shall not be an incomplete or function type . The optional type qualifiers and the keyword static shall appear only in a declaration of a function parameter with an array type, and then only in the outermost array type derivation.

Because pointers are not incomplete types or function types you can create arrays of them even if they point to incomplete types. A pointer to an incomplete type doesn't make the pointer somehow incomplete. You just can't de-reference it and hope to do anything useful with it directly . I say directly because in data hiding techniques and opaque pointers you can provide indirect mechanisms to work with the opaque pointers data..

Here is an example of code that should fail to compile in a similar way the OPs did. We take the idea that pointers to incomplete types can be passed around (function arguments) but they still can't be used as an array inside a function:

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

This is nearly identical to the previous one. In this one we have a pointer sv to an incomplete type as a function argument (this is from nneonneo answer). This is completely legal since it is just a pointer. However attempting to do array arithmetic on it (using ++ in the body function) will fail because it needs to know the size of element and it isn't known. ++ and -- or to index an array are undefined behavior (and most standard compliant compilers will throw an error). ISO/IEC 9899:TC2 says:

6.3.2 Other operands

6.3.2.1 Lvalues, arrays, and function designators

...

2 Except when it is the operand of the sizeof operator, the unary & operator, the ++ operator, the -- operator, or the left operand of the . operator or an assignment operator, an lvalue that does not have array type is converted to the value stored in the designated object (and is no longer an lvalue). If the lvalue has qualified type, the value has the unqualified version of the type of the lvalue; otherwise, the value has the type of the lvalue. If the lvalue has an incomplete type and does not have array type, the behavior is undefined

More on opaque types can be found here

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM