C empty struct -- what does this mean/do?

I found this code in a header file for a device that I need to use, and although I've been doing C for years, I've never run into this:

struct device {
};

struct spi_device {
    struct device dev;
};

and it used as in:

int spi_write_then_read(struct spi_device *spi, 
const unsigned char *txbuf, unsigned n_tx,
unsigned char *rxbuf, unsigned n_rx);

and also here:

struct spi_device *spi = phy->spi;

where it is defined the same.

I'm not sure what the point is with this definition. It is in a header file for a linux application of the board, but am baffled by it use. Any explanations, ideas? Anyone seen this before (I'm sure some of you have :).

Thanks! :bp:


This is not C as C structures have to contain at least one named member:

(C11, 6.7.2.1 Structure and union specifiers p8) "If the struct-declaration-list does not contain any named members, either directly or via an anonymous structure or anonymous union, the behavior is undefined."

but a GNU C extension:

GCC permits a C structure to have no members:

struct empty {
};

The structure has size zero

https://gcc.gnu.org/onlinedocs/gcc/Empty-Structures.html

I don't know what is the purpose of this construct in your example but in general I think it may be used as a forward declaration of the structure type. Note that in C++ it is allowed to have a class with no member.

In Linux 2.4 there is an example of an empty structure type with conditional compilation in the definition of spin_lock_t type alias in Linux kernel 2.4 (in include/linux/spinlock.h):

#if (DEBUG_SPINLOCKS < 1)

/* ... */

typedef struct { } spinlock_t;

#elif (DEBUG_SPINLOCKS < 2)

/* ... */

typedef struct {
    volatile unsigned long lock;
} spinlock_t;

#else /* (DEBUG_SPINLOCKS >= 2) */

/* ... */

typedef struct {
    volatile unsigned long lock;
    volatile unsigned int babble;
    const char *module;
} spinlock_t;

#endif

The purpose is to save some space without having to change the functions API in case DEBUG_SPINLOCKS < 1. It also allows to define dummy (zero-sized) objects of type spinlock_t.

Another example in the (recent) Linux kernel of an empty structure hack used with conditional compilation in include/linux/device.h:

struct acpi_dev_node {
#ifdef CONFIG_ACPI
    void *handle;
#endif
};

See the discussion with Greg Kroah-Hartman for this last example here:

https://lkml.org/lkml/2012/11/19/453


This is not standard C.
C11: 6.2.5-20:

— A structure type describes a sequentially allocated nonempty set of member objects (and, in certain circumstances, an incomplete array), each of which has an optionally specified name and possibly distinct type.

J.2 Undefined behavior:

The behavior is undefined in the following circumstances:
....
A structure or union is defined without any named members (including those specified indirectly via anonymous structures and unions) (6.7.2.1).

GCC uses it as an extension (no more detailed is given there about when/where should it be used). Using this in any program will make it compiler specific.


One reason might to do this for a library is that the library developers do not want you to know or interfere with the internals of these struct. It these cases they may provide an "interface" version of the structs spi_device/device (which is what you may see) and have a second type definition that defines another version of said structs for use inside the library with the actual members.

Since you cannot access struct members or even create compatible structs of that type yourself with that approach (since even your compiler would not know the size actual size of this struct), this only works if the library itself creates the structs, only ever passes you pointers to it, and does not need you to modify any members.


If you add an empty struct as the first member of another struct, the empty struct can serve as a "marker interface", i.e. when you cast a pointer to that outer struct to a pointer of the inner struct and the cast succeeds you know that the outer struct is "marked" as something.

Also it might just be a place holder for future development, not to sure. Hope this helps