Hacker News new | ask | show | jobs
by dnquark 3439 days ago
The trick to reading crazy C declarations is learning the "spiral rule": http://c-faq.com/decl/spiral.anderson.html (here are more examples, with nicer formatting: http://www.unixwiz.net/techtips/reading-cdecl.html)
3 comments

The "spiral rule" doesn't work all the time. See this previous HN comment by stephencanon: https://news.ycombinator.com/item?id=12775862 .

Also this comment by Linus Torvalds (copy pasted because I don't know how to link to Google+ comments):

> I don't think that works. It breaks trivially for consecutive [] or * cases, something that he carefully didn't have in his examples.

> So the examples were made up to make it look like it's a spiral, but type parsing is about precedence, not about spirals. It so happens that the higher-precedence operators ([] and ()) are on the right-hand side, which is why it "works" to start on the right.

> And it doesn't explain why

> typedef int (fn_t[][2])(void);

> is ok, but

> typedef int (fn_t[2][])(void);

> is not.

> "Spirals"? I don't think so.

All these years I assumed that the 'spiral rule' and 'right-left rule' (linked from the stephencanon comment above, and also described in my second link) are two ways to describe the same algorithm, but, reading closely, they aren't! I guess 'spiral rule' is a stickier name, which is why it's something that people remember even though it's janky.

    typedef int *(**fn_t[][2])(void);
is ok, but

    typedef int *(**fn_t[2][])(void);
is not.

(fixed formatting?)

No, the trick is to remember that declaration follows use. Declare a symbol using (nearly) the same exact syntax you would use to extract a value of the base type from that symbol.

See also my comment last time this subject came up: https://news.ycombinator.com/item?id=12775966

And yet so many people learn

  int* p; // p is an int pointer
instead of

  int *p; // dereferencing p will give an int
I know this is the subject of holy wars, but once I'd seen the second one my eyes were opened and I had way less trouble. I think that declaration follows use is another of example of the amazing design powers of the patriarchs.
Considering that

    int * foo, bar;
are variables of two different types, the asterisk clearly has affinity to the variable name. It is misleading to bind it to the base type name.
how do you remember a const pointer like

> int * const a

? i.e. in what way do you extend your scheme such that it makes sense again?

'a' is a constant that can be dereferenced to get int.
My brain parses both of your expressions (and both of your comments) the same way.

Like, it's tautological: 'de-referencing an int pointer will give you an int'

Brains are weird.

Mine does too, but that's because I've trained myself to see it that way in order to understand this exact concern.
> amazing design powers of the patriarchs

Thanks for elevating their gender specifically.... gosh forbid that Ada had any amazing design powers.

Not the OP, but given that English is historically gender biased, I find it pretty easy to unconsciously fall into the trap of using a gender biased turn of phrase. Even if one is consciously trying to avoid such things, our messy neural nets being what they are, mistakes happen.

Given the almost certainty (in my mind anyway), that the OP meant no disrespect to people who identify with genders that are not male, it would make me very happy indeed if requests for correction could be made in a respectful way. Simply asking something like "Would you mind using the alternative phrasing 'blah blah blah'" would go a long way towards helping everyone to maintain a civil tone.

Wasn't C designed by a man?
A couple of 'em!
Thanks for caring about gendered language. I used this term because it's used (humorously) in the Unix Koans[0]. But maybe you're reading to far into it, I certainly don't mean to imply their gender had anything to do with C's design, but for better or worse they did happen to be men and were seen by by some as a "father figure" for the language.

[0]http://catb.org/esr/writings/unix-koans/zealot.html

So, applied to the examples:

    As a variable:
    returnType (*variableName)(parameterTypes) = function_name;
variableName is a pointer to a function that accepts parameterTypes and returns returnType

    As a static const variable:
    static returnType (* const variableName)(parameterTypes) = function_name;
variableName is a const pointer to a function that accepts parameterTypes and returns static returnType (or maybe variableName is the static thing?)

    As an array:
    returnType (*arrayName[])(parameterTypes) = {function_name0, ...};
arrayName is an array of pointers to functions that accept parameterTypes and return returnType

    As an argument to a function:
    int my_function(returnType (*argumentName)(parameterTypes));
my_function is a function that accepts an argumentName, which is a pointer to a function accepting parameterTypes and returning returnType, and returns an int

    As a return value from a function:
    returnType (*my_function(int, ...))(parameterTypes);
my_function is a function that accepts int and other parameters and returns a pointer to a function that accepts parameterTypes and returns returnType

    As a typedef:
    typedef returnType (*typeName)(parameterTypes);
a typeName is now a pointer to a function that accepts parameterTypes and returns returnType