Ways to get screwed by C
andromeda.com
andromeda.com
I don't think they even make compilers that don't warn about the top 2 issues, and 5 for sure. And after that it gets down to "I did something stupid and something stupid happened." I mean:
int ii = i/++i;
Seriously? As if defining the order of operations would magically take the suck out of that statement.There are like 1.5 nuggets of real pain in here. (1 point for returning a stack array, 0.5 points for noticing that C is C.)
#9:
#define DEVICE_COUNT 4
uint8 *szDevNames[DEVICE_COUNT] = {
"SelectSet 5000",
"SelectSet 7000"}; /* table has two entries of junk */
"junk" is incorrect. It has 2 entries of zeros, and that is something that you can count on. If you really don't like that (and I generally do like that) you can turn on gcc's -Wextra which will warn about it.#17 complains about char's being signed but really it should be complaining about chars' signedness not being spec-ed (really--it's implementation dependent whether plain "char" is signed or unsigned. If you really care, use the "signed" keyword. But really, complaining about overflow? Strange.
My favorite example is from #19, which is something that javascript programmers do all of the time.
int value = a && b && fn(a->x,b->x);
(The author goes on to complain about how value you obviously be whatever fn(a->x, b->x) returns).In javascript, that's actually good practice (I guess, I had a mentor once who encouraged it, but I never really bought into it). To a C programmer though, that just looks gross. (Or at least to me, and I like to pretend to be a C programmer).
That was bad.
Use a sane editor with syntax highlighting.
2. Accidental assignment/Accidental Booleans
I always wrap my assignment-conditionals with double parentheses. It sucks when you miss these but I usually type out the right sequence of equal signs when I mean equality.
3. Unhygienic macros
Treat macros like a search-and-replace with a little more intelligence, but respect how literally the pre-processor might take you for. So, add parentheses.
4. Mismatched header files
I've not encountered this before, so I can't comment on it. Be careful with namespaces.
5. Phantom Returned Values
Luckily, gcc -Wall returns: warning: control reaches end of non-void function
6. Unpredictable struct construction
Can't comment on this one either, although I avoid literal assignments given in the example like the plague.
7. Indefinite order of evaluation
The example code just looks messy.
8. Easily changed block scope
Always use curly braces, that's what I say.
9. Permissive compilation
Not sure in the example why one would just remove the CALLIT macro and assume things to work. Of course, the comma in C means something. Not sure why one would put an assignment before a case in a switch either.
10. Unsafe returned values
This is certainly expected!
My biggest gripe with C is string handling. Although the extremely insecure functions have been slowly phased out (such as gets), zero-terminated strings are an attack on sanity.
There are some nice safe string libraries out there, like this one:
Still, I wish something like that was simply built-in, as external string libraries can cause interoperability issues: Each framework defines its own string handling functions and format, making it neccesary to convert between them in an application, if you use them together.
(the worst thing is that this problem still exists with C++ as of today, even though it has a built-in string people insist on rolling their own)
My employer had code that ran on about 5 billion different platforms, and my job was to port it to a new one. Everything went fine, except for the weird random crashes that would happen periodically. The idea that a "char" could be an "unsigned char" by default was so far off my radar screen that I didn't figure out what was going on until a few days later when I reluctantly dived into the assembly. (It didn't help that it was a mobile platform with basically 0 support for gdb).
Turned out to be a 3 second fix - pass "-fsigned-char" to gcc. Nowadays in new code I always explicitly declare whether my chars are signed or unsigned.
It's only when you start doing arithmetic on characters, or just want to use char to mean "byte" (or "octet") that it matters, and then it's a very good idea to be specific and say "unsigned char" if that is what you expect.
#define DEVICE_COUNT 4
uint8 *szDevNames[DEVICE_COUNT] = {
"SelectSet 5000",
"SelectSet 7000"}; /* table has two entries of junk
*/
Actually, the remaining two entries are 0, they are not junk.Some of the other complaints are valid—for example, I dream of a sensible module system when writing in C, which would alleviate #13—but a lot of the complaints are sort of petty, and I'd argue that this one, being true of many more languages than C, falls directly into the petty bin.
Luckily we have function prototypes in C nowadays ;-)
Also no mention of integer overflow handling? http://www.pixelbeat.org/programming/gcc/integer_overflow.ht...
I use Enhanced CWEB for C programming. Many of these things will be caught because you can see in the printout of the book, that there are mistakes. (For example, it typesets octal numbers in italic)
struct thing {
int x;
} aThing;
int x;
aThing,x = 3;
Do you see it?EDIT: fix typo
Or use "gcc -Werror" when compiling.
C is like a sharp knife: in skilled hands, it can do wonders. Unskilled hands end up missing a finger or two.
Runs and ducks.
for(i=0;i<10;i++); { /some code here/ }
I liked
int a = 2 && 4 && 8; // what is the value of "a" ? would you belive a=1 ?
I'm not sure the author has the necessary qualifications to condemn C's "poor design."