Seems an odd example: any code with a multiline comment can have the same issue. That's nothing to do with the strangeness of Duff's device. It would work the same in a series of function calls or calculations.
And even then it is a matter of experience. It looks odd to me to have multiline comments not in its own 'paragraph' of the code, so draws the eye right away. Perhaps this would foil some programmers, but not more than once, I'd have thought. In my experience, run-on compound statements are much more common and hard to intuitively spot:
if (foo)
bar();
sun();
Duff's device is difficult to understand from first principles, but even that is a bad example of unmaintainable code because a) it looks like nothing except Duff's device, you only need to see the pattern once or twice and you'd recognise it, and at least know 'it's that weird pattern for unrolling loops', and b) it is a performance optimisation that only belongs in code that is profiled and needs to go that fast. As such it should be well commented to avoid regressions by well meaning refactor-zealots. Inline assembly or heavy intrinsics are more difficult to read than regular C too, so you only use them when you need to. In my experience manual loop unrolling is very rarely needed.
Duff's is not just about unrolling, it's mostly a way to use the 'case can be anywhere inside a switch statement'. It's used a lot for other purpose, some of them very, very handy. The stackless coroutine trick is very useful for example (see http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html )
On platforms with very little RAM (embedded) it's fantastic to be able to 'port' linear code that might have run in a thread, and convert it to a coroutine that is a lot easier to read and maintain than having to convert it all up to a 'manual' state machine.
Note that with gcc, you can reach the same results by using the 'indirect goto' that is both more powerful, and more dangerous!
void * lab;
lab = &my_label;
goto *lab;
my_label: ...
"You can typedef a function declaration... and declare function prototypes..."
This is actually a very useful technique that I use all the time. It allows you to make sure that a function and any function pointers that point to it always have matching types (since you only have to change the prototype in one place - the typedef).
The bitfield example is misleading. Section 6.7.2.1/10 of the C99 standard says:
"The order of allocation of bit-fields within a unit (high-order to low-order or low-order to high-order) is implementation-defined"
There is no guarantee on the order of the bits inside a bitfield. The compiler may also introduce padding, e.g. for alignment purposes. This makes bitfields unusable for unpacking binary data.
Unfortunately, you're stuck with shifting and masking to replicate the same effect.
That's one of the few nag I have about C; this syntax is fantastic, and theoretically allow the compiler quite a lot of freedom to optimize access to the bitfield, and the fact that it was never standardized means you just can't use it if you want portable code.
*(const char * + char *) The type of int i is converted to 'char *' and multiplied by sizeof(char)
I am pretty certain this explanation does not make any sense: what is really happening here is that the int, for purposes of the addition, is measuring units sizeof the object being pointed to; there is no meaning I know of to adding two pointers.
/* This works because "Hello"[5] == 5["Hello"].*/
At this point, you could really just say the following:
/* This works because a[b] == *(a + b), and addition is commutative. */
means three different things, depending on where it appears: As a function argument, it is a pointer that will be accessed like an array. As a variable with automatic storage duration, it is an array whose size will be determined by the right-hand-side of an assignment from a braced initializer list. In the middle of a struct definition, it's illegal. And at the end of a struct definition, it is a flexible array member.
AFAIK this this was only made legal by c99. Though I have seen it in code that is much older. (Pre-c99 you would give the "flexible" member an element size inside the square brackets, such as 0 or 1, but allocate as if it were a larger size on the heap.)
Unions? Function pointers? Typedefs? While it might be bad for karma to point out, intro to C certainly isn't what I expect to be news to "hackers", as per the site's namesake.
To consider function types, or unjustified assumptions about bitfield unions, or use of parentheses to control nesting of arrays and pointers in declaration "strange" one must be averse to the C language to the point of intolerance. Backlash from working on a C compiler and wishing the task was easier?
I had an "ohh wow" moment when I realized that the keyword typedef is a storage class. This means it can go anywhere static can go. It just means no variable is introduced, only a type name, otherwise it is the same syntax as declarations.
From C11:
If the member used to read the contents of a union object is not the same as the member last used to
store a value in the object, the appropriate part of the object representation of the value is reinterpreted
as an object representation in the new type as described in 6.2.6 (a process sometimes called ‘‘type
punning’’). This might be a trap representation.
union {
int x;
float y;
} b;
b.y = 10e5;
printf("%x\n", b.x);
That behavior is legal and well-defined behavior (up to the implementation-defined nature of representations) under C99 and C11, but not under C89 and earlier. Unfortunately, although it was made legal in C99, C99 did retain the program as an example in its (non-normative) list of undefined behaviors, which doesn't help clear up its legality.
Its status under C++11 and C++14 is much more debatable. I recall (I may have bad memory) that an early draft of C++0x had incorporated new C99 text on unions, which would have made it legal, but the wording of unions changed dramatically when unrestricted unions were introduced, which means that assessing its present legality relies very heavily on how you extend initialization to types like int and float.
Strictly speaking accessing that union both ways ~~~violates the strict aliasing rules~~~ isn't portable. However, it is such a common idiom that GCC and other compilers explicitly allow using unions to get around the strict aliasing rules, so long as the access is always performed through the union.
Isn't the first example undefined behavior? I always thought you shouldn't assign data to a union using one member, then access the data using a different member.
> All of these examples you'll see here will compile without warnings or errors even with very strict compiler flags in gcc and clang (gcc -Wall -ansi -pedantic -std=c89 main.c)
Umm, that's really not that restrictive. Use `clang -Weverything -std=c11 main.c` if you want strict warnings.
I've found that a great deal of these idioms are explained in the excellent book "Advanced C Programming: Deep C Secrets" by Peter van der Linden. Its one of my goto books for when I want to enhance my 30 years of C-programming experience with a little more insight - I've read it multiple times since it was published, and always learn something new. Check it out if you want to dive more deeply into some of these oddities:
The rare technical book that rewards the reader with not merely technical excellence but also robust humor. I still read parts on occasion even though I have no need for C these days.
[+] [-] cjslep|10 years ago|reply
[+] [-] sago|10 years ago|reply
And even then it is a matter of experience. It looks odd to me to have multiline comments not in its own 'paragraph' of the code, so draws the eye right away. Perhaps this would foil some programmers, but not more than once, I'd have thought. In my experience, run-on compound statements are much more common and hard to intuitively spot:
Duff's device is difficult to understand from first principles, but even that is a bad example of unmaintainable code because a) it looks like nothing except Duff's device, you only need to see the pattern once or twice and you'd recognise it, and at least know 'it's that weird pattern for unrolling loops', and b) it is a performance optimisation that only belongs in code that is profiled and needs to go that fast. As such it should be well commented to avoid regressions by well meaning refactor-zealots. Inline assembly or heavy intrinsics are more difficult to read than regular C too, so you only use them when you need to. In my experience manual loop unrolling is very rarely needed.[+] [-] buserror|10 years ago|reply
Note that with gcc, you can reach the same results by using the 'indirect goto' that is both more powerful, and more dangerous! void * lab; lab = &my_label; goto *lab; my_label: ...
[+] [-] marcosdumay|10 years ago|reply
I liked a lot how you switch on count and loop on j. If gcc didn't comply, I would never notice it.
[+] [-] 0x400614|10 years ago|reply
[+] [-] skarap|10 years ago|reply
[+] [-] greenyoda|10 years ago|reply
This is actually a very useful technique that I use all the time. It allows you to make sure that a function and any function pointers that point to it always have matching types (since you only have to change the prototype in one place - the typedef).
[+] [-] kabouseng|10 years ago|reply
[+] [-] TwoBit|10 years ago|reply
[+] [-] m3koval|10 years ago|reply
"The order of allocation of bit-fields within a unit (high-order to low-order or low-order to high-order) is implementation-defined"
There is no guarantee on the order of the bits inside a bitfield. The compiler may also introduce padding, e.g. for alignment purposes. This makes bitfields unusable for unpacking binary data.
Unfortunately, you're stuck with shifting and masking to replicate the same effect.
[+] [-] gdwatson|10 years ago|reply
[+] [-] buserror|10 years ago|reply
[+] [-] saurik|10 years ago|reply
[+] [-] skarap|10 years ago|reply
struct items_with_header { int header_field1; unsigned int length; double array[]; };
Then allocate enough memory and use the struct to access it.
Used it once in a hash-table implementation.
[+] [-] brandmeyer|10 years ago|reply
typename identifier[];
means three different things, depending on where it appears: As a function argument, it is a pointer that will be accessed like an array. As a variable with automatic storage duration, it is an array whose size will be determined by the right-hand-side of an assignment from a braced initializer list. In the middle of a struct definition, it's illegal. And at the end of a struct definition, it is a flexible array member.
[+] [-] asveikau|10 years ago|reply
[+] [-] sepeth|10 years ago|reply
https://github.com/sepeth/python-skiplist/commit/00ae7f94246...
Redis's skiplist is also using the same feature:
https://github.com/antirez/redis/blob/unstable/src/server.h#...
[+] [-] white-flame|10 years ago|reply
[+] [-] HelloNurse|10 years ago|reply
[+] [-] andrewchambers|10 years ago|reply
[+] [-] evincarofautumn|10 years ago|reply
[+] [-] drauh|10 years ago|reply
[+] [-] biot|10 years ago|reply
[+] [-] thwest|10 years ago|reply
[+] [-] jcranmer|10 years ago|reply
union { int x; float y; } b; b.y = 10e5; printf("%x\n", b.x);
That behavior is legal and well-defined behavior (up to the implementation-defined nature of representations) under C99 and C11, but not under C89 and earlier. Unfortunately, although it was made legal in C99, C99 did retain the program as an example in its (non-normative) list of undefined behaviors, which doesn't help clear up its legality.
Its status under C++11 and C++14 is much more debatable. I recall (I may have bad memory) that an early draft of C++0x had incorporated new C99 text on unions, which would have made it legal, but the wording of unions changed dramatically when unrestricted unions were introduced, which means that assessing its present legality relies very heavily on how you extend initialization to types like int and float.
[+] [-] brandmeyer|10 years ago|reply
[+] [-] unknown|10 years ago|reply
[deleted]
[+] [-] nemesisrobot|10 years ago|reply
[+] [-] skarap|10 years ago|reply
[+] [-] halosghost|10 years ago|reply
Umm, that's really not that restrictive. Use `clang -Weverything -std=c11 main.c` if you want strict warnings.
[+] [-] fit2rule|10 years ago|reply
http://archive.arstechnica.com/etc/books/deep-c.html
[+] [-] macintux|10 years ago|reply