Empty structs are supported in clang; I think it's a GCC extension to C. This program:
#include <stdio.h>
typedef struct {} unit;
static unit f(unit *p)
{
return (unit){};
}
static void g(unit u)
{
}
int main()
{
unit a = {}, b = {};
a = b;
f(&a);
g(b);
printf("a is at %lx, b is at %lx, "
"sizeof unit is %d\n",
(unsigned long)&a, (unsigned long)&b,
(int)sizeof(unit));
return 0;
}
compiles without complaints on my cellphone and produces the output:
a is at 7ffb39805b, b is at 7ffb39805a, sizeof unit is 0
So you can declare empty structs as variables, return them, assign them, pass them as parameters, take their addresses, create them in struct literals, and dereference pointers to them. clang is assigning different addresses to different empty-struct local variables, but presumably in an array all of them would have the same address, unlike in C++.
I wouldn't be confident that you could malloc them, and I wouldn't be surprised if passing them by value uncovered compiler divergences in the interpretation of the ABI. (I spent most of last night tracking down a bug due to LuaJIT/GCC ABI differences in the implementation of parameter passing.)
Why couldn't you malloc them? Malloc doesn't care about types, it just wants the number of bytes, and 0 is a perfectly valid value to pass to malloc. Now, it's unspecified whether malloc will give you back a pointer distinct from all other non-freed allocated blocks or NULL, but if you don't rely on address as identity that is irrelevant.
You're right, in the standard it's implementation-defined. I mistakenly thought malloc(0) was undefined. That said, it's probably not the best-tested code path in the system library.
In Delphi, and possibly other Pascal variants, you can do this with a type that's just an empty record (struct). It'll have zero size, but you can declare variables of that type, pass it as parameters and such.
IIRC things get a bit funky using this in certain situations, so I'm not sure the compiler devs actually considered this or if it's just a happy accident of sorts.
The problem is that it's still a nominal type system, so each empty struct is still a different type. A proper unit type has a single value of that type, and it's the same throughout the entire program.
> The problem is that it's still a nominal type system, so each empty struct is still a different type.
Fair point, though for me that was a feature when I used it in Delphi. Allowed me to differentiate them when using them as type parameters (generics).