2

Ok, In 15 years of writing C code I have never seen code like this, and I don't know how it works. It centers around some C99 code where somehow multiple lines of code get resolved to an integer value. It comes from libplist, specifically here. Ok, it starts on line 394, where a uint64_t member of a struct, is assigned the result of a macro UINT_TO_HOST

data->intval = UINT_TO_HOST(&data->intval, size);

UINT_TO_HOST is a macro that takes a pointer to an unsigned integer, and the size in btes of the integer. the macro defines a union to a set of pointers of different uint sizes, then sets the address to the uint pointer passed to the macro. It then proceeds to align the uint in memory, in order to properly byte swap it with another macro. UINT_TO_HOST is defined on line 172

#define UINT_TO_HOST(x, n) \
({ \
    union plist_uint_ptr __up; \
    __up.src = x; \
    (n == 8 ? be64toh( get_unaligned(__up.u64ptr) ) : \
    (n == 4 ? be32toh( get_unaligned(__up.u32ptr) ) : \
    (n == 3 ? uint24_from_be( __up ) : \
    (n == 2 ? be16toh( get_unaligned(__up.u16ptr) ) : \
    *__up.u8ptr )))); \
})

get unaligned does a trick with a struct and the gcc attribute packed, which returns the pointer value aligned in memory based on the type.

  #define get_unaligned(ptr)              \
  ({                                              \
    struct __attribute__((packed)) {          \
      typeof(*(ptr)) __v;             \
    } *__p = (void *) (ptr);              \
    __p->__v;                     \
  })

be64toh simply does some in place masking and shifting.

#define be64toh(x) ((((x) & 0xFF00000000000000ull) >> 56) \
                | (((x) & 0x00FF000000000000ull) >> 40) \
                | (((x) & 0x0000FF0000000000ull) >> 24) \
                | (((x) & 0x000000FF00000000ull) >> 8) \
                | (((x) & 0x00000000FF000000ull) << 8) \
                | (((x) & 0x0000000000FF0000ull) << 24) \
                | (((x) & 0x000000000000FF00ull) << 40) \
                | (((x) & 0x00000000000000FFull) << 56)) 

The question is how does UINT_TO_HOST actually return a value into data->intval, since UINT_TO_HOST basically gets resolved to 3 lines of code inside a set of curly braces {}. The same thing appears to happen inside of get_unaligned, where presumably the last statement

__p->_v;

Is what gets returned. What is this feature called, and can anyone point to some documentation on this 'feature'?

hokey
  • 63
  • 3

2 Answers2

4

This is called a statement expression, and it is a GNU extension. Here's some documentation you may want to read.

  • Brilliant! Thanks H2CO3, that answers my question. Taken from the link you provided on statement expressions: The last thing in the compound statement should be an expression followed by a semicolon; the value of this subexpression serves as the value of the entire construct. – hokey Aug 03 '13 at 21:13
  • @hokey You're welcome. Seeing that you're a veteran C programmer (and probably much more experienced than me): you are perhaps interested in/sensitive to getting used to good practices. You haven't seen this construct in the last one and a half decades for a very good reason. This is not standard, not portable and as such, I'd advise you to avoid it completely. –  Aug 03 '13 at 21:16
  • I agree. It caused me a decent headache this week trying to port the code. Macro's have their place, but their lack of type safety and other evils [link](http://www.parashift.com/c++-faq/inline-vs-macros.html) just makes the code difficult to maintain, read, and prone to error. Not to mention good luck debugging this. – hokey Aug 03 '13 at 22:16
0

As was pointed out by others, the macro uses the GCC syntax of "statements in expressions".

But the macro itself is not well formed. Be carefull. The code of that macros can be broken, because the presence of { } inside.
For example:

union plist_uint_ptr __up;
__up.src = 3;
UINT_TO_HOST(__up.src, 8);

After macro expansion it is obtained the sentence: __up.src = __up.src; which only can give a garbage value, because the object __up has been defined again in the block scope of the macro.

This kind of trouble was recently discussed here (first part of section 1 and then jump to section 5):

How much is it possible to create fake-functions with macros in C?

The problem is that the inner variable __up hides the value of the external variable __up, and the macro does not work as expected.
So, it is non-reusable code.

Community
  • 1
  • 1
pablo1977
  • 4,281
  • 1
  • 15
  • 41