Hacker News new | past | comments | ask | show | jobs | submit login

In practice, nobody uses a "clockwise/spiral rule" to read C declarations; they typedef complex declarations into bite-sized components.

C declarations are hardest to understand when they involve (1) "arrays"† of "arrays" of "arrays", (2) function pointers, and (3) multiple layers of indirection.

In systems and networking code and in most application code, (1) "multi-level arrays" are uncommon, and they're uncommon in roughly the same way tuples of tuples of tuples are uncommon in Python: they're a symptom that you're missing a level of abstraction.

Function pointer declarations (2) are common and annoying to read. However, in virtually all cases, the core of what you're trying to express with a function pointer declaration is "what arguments does this function take" and "what does it return". More importantly, most idiomatic C code typedefs function pointers, so you're not looking at prototype decls with nested complex function pointer decls inside of them.

So instead of

    void (*signal(int sig, void (*func)(int)))(int);
a modern C programmer would expect

     typedef void (*sig_t) (int);

     sig_t signal(int sig, sig_t func);
The qsort(3) man page is another example of an archaic declaration (its authors presumably think this is the clearest way to convey qsort to an experienced programmer); check out the man page for pcap_loop(3) for a better example.

In most C code, multiple layers of indirection (3) are one-step pointers-to-pointers used to work around call-by-value in C. If you store in your head the notion that a pointer to a pointer is just there to provide a return-value argument, that's probably all you'd need to remember about it. Pointers to pointers to pointers are rare.

C pedants: I'm using "array" in an intentionally vague sense.




I make a rule of always typedefing function pointer types. I've never seen a single case where it didn't make things cleaner and more readable, not to mention that it's significantly easier to change the declarations in the future, since you rarely have a single place where you define and use a function pointer.


I like types. I do have a question though: Why do you call it "sig_t" as opposed to "sig_h" or something. Based on its positional appearance, it's pretty clear that it's a type. Or does t stand for something else?


It's a POSIX convention to indicate that it's a typedef.


User code built against POSIX should never use the _t suffix; POSIX explicitly puts *_t into the reserved namespace (http://pubs.opengroup.org/onlinepubs/007904975/functions/xsh...), meaning that future revisions may add arbitrary type names of that form, which will break your code if you use the same name and include POSIX headers.


Thbthbthbthbthbththbthtbhtbhthbt! I say to POSIX. This convention is practically universal.


Just so long as I don't see you filing any bug reports when POSIX changes a header and your code stops compiling. =)


That's why I use *__t (two underscores).


That's not ideal either.

http://www.doc.ic.ac.uk/lab/cplus/c++.rules/chap5.html

"The use of two underscores (`__') in identifiers is reserved for the compiler's internal use according to the ANSI-C standard."


Are you sure that statement is correct? In my memory, it's only double underscores at the start of identifiers. My final draft of ISO C 11 seems to confirm that:

"7.1.3 Reserved identifiers 1 Each header declares or defines all identifiers listed in its associated subclause, and optionally declares or defines identifiers listed in its associated future library directions subclause and identifiers which are always reserved either for any use or for use as file scope identifiers. — All identifiers that begin with an underscore and either an uppercase letter or another underscore are always reserved for any use. — All identifiers that begin with an underscore are always reserved for use as identifiers with file scope in both the ordinary and tag name spaces. — Each macro name in any of the following subclauses (including the future library directions) is reserved for use as specified if any of its associated headers is included; unless explicitly stated otherwise (see 7.1.4). — All identifiers with external linkage in any of the following subclauses (including the future library directions) and errno are always reserved for use as identifiers with external linkage.184) — Each identifier with file scope listed in any of the following subclauses (including the future library directions) is reserved for use as a macro name and as an identifier with file scope in the same name space if any of its associated headers is included. 2 No other identifiers are reserved. If the program declares or defines an identifier in a context in which it is reserved (other than as allowed by 7.1.4), or defines a reserved identifier as a macro name, the behavior is undefined."


You are right, it appears to be C++-specific and looks like a typo in the link.

C++98, 17.4.3.1.2 Global names

- Each name that contains a double underscore (__) [...] is reserved to the implementation for any use.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: