Gcc transparent union example

Up ../

Gcc transparent union example

Compiling network code under RHEL5.8 started giving errors when __GNU_SOURCE is defined. The system headers for sys/socket.h etc had been updated to use a gcc feature called transparent unions. This allows functions like connect(2) which take a generic pointer to a specific socket struct to omit the cast. While this makes the caller side simpler (re)implementing the called function requires some understanding and changes.

To get my head around this feature I put together a very simple example.

//	@(#) transparent_union_example.c
//	See http://gcc.gnu.org/onlinedocs/gcc/Type-Attributes.html
//   ... 
//   transparent_union
//      This attribute, attached to a union type definition, indicates
//   that any function parameter having that union type causes calls to
//   that function to be treated in a special way.
//      First, the argument corresponding to a transparent union type
//   can be of any type in the union; no cast is required. Also, if the
//   union contains a pointer type, the corresponding argument can be
//   a null pointer constant or a void pointer expression; and if the
//   union contains a void pointer type, the corresponding argument can
//   be any pointer expression. If the union member type is a pointer,
//   qualifiers like const on the referenced type must be respected,
//   just as with normal pointer conversions.
//      Second, the argument is passed to the function using the calling
//   conventions of the first member of the transparent union, not the
//   calling conventions of the union itself. All members of the union
//   must have the same machine representation; this is necessary for
//   this argument passing to work properly.
//   ...

# include	<stdio.h>

enum	argtype	{
		T_STRING	= 's',
		T_POINT		= 'p',
typedef	enum	argtype	ARGTYPE;	

struct	point	{
	ARGTYPE	type;
	float	x;
	float	y;
typedef	struct	point	POINT;

struct	string	{
	ARGTYPE	type;
	char*	s;
typedef	struct	string	STRING;

union	example	{
	POINT*	pt;
	STRING*	str;	
typedef	union	example	EXAMPLE __attribute__ ((__transparent_union__));

void	transparent_union_example (EXAMPLE arg) {
	ARGTYPE	type	= arg.str->type; /* arg.pt->type would also do */
	switch (type) {
	case	T_STRING: {
		char*	s	= arg.str->s;
		printf ("string='%s'\n", s);
	case	T_POINT: {
		POINT*	pt	= arg.pt;
		printf ("p(x,y)=(%f,%f)\n", pt->x, pt->y);

main() {
	POINT	pt	= { T_POINT, 0.5, 7.5 };
	STRING	str	= { T_STRING, "Test" };
	transparent_union_example (&str);	/* no casts required */
	transparent_union_example (&pt);

Source files

If you are intercepting functions like connect(2) you need to spelunk through sys/socket.h" to get at the union member names. Looking at the definition of __CONST_SOCKADDR_ARG which is just

	__const struct sockaddr *
for non gnu and older ( < 2.7 gnu) compilers. But for recent gcc the definition is a typedef-ed union
union {
} __CONST_SOCKADDR_ARG __attribute__ ((__transparent_union__))

// ALLTYPES is macro constaining a union member for each socket type
// Abreviated here:
                      __SOCKADDR_ONETYPE(sockaddr_in) \
                      __SOCKADDR_ONETYPE (sockaddr_un)
// And finally __SOCKADDR_ONETYPE does macro magic
__SOCKADDR_ONETYPE(type) __const struct type *__restrict __##type##__;

// So that __SOCKADDR_ONETYPE(sockaddr_in) expands into:
__const struct type *__restrict __sockaddr_in__;

// Putting it all together (abreviated)
typedef union  {
	__const struct type *__restrict __sockaddr__;
	__const struct type *__restrict __sockaddr_in__;
	__const struct type *__restrict __sockaddr_un__;
}  __CONST_SOCKADDR_ARG __attribute__ ((__transparent_union__));

So reimplimenting connect(2)

int connect (int fd, __CONST_SOCKADDR_ARG addr, socklen_t len) {
/* All these are equivalent when using a transparent_union
	addr.__sockaddr__    -> sa_family;
	addr.__sockaddr_in__ -> sin_family;
	addr.__sockaddr_un__ -> sun_family;
without the union either
	(const struct sockaddr_in*) (addr)->sin_family;
would have done.

/* One approach is just revert to the previous way for both.
	const struct sockaddr*	sa	=
# else
# endif
	sa_family_t	family	= sa->sa_family;

Creative Commons CC0 http://creativecommons.org/publicdomain/zero/1.0/legalcode

James Sainsbury