9 ms·
How to read C type declarations (2003)
- rep_lodsb 2y agoC syntax is just horrible. It would be better to have something like "ARRAY [100] OF POINTER TO FUNCTION RETURNING POINTER TO INT", if you need it more than once or twice you can declare a type for it.
- bregma 2y agoYou missed the opening "PROCEDURE DIVISION.".
- ape4 2y agoADD A TO B GIVING C would be so much clearer
- pyjarrett 2y agoAda syntax is close to what you described: Foo : array (Positive range 1 .. 100) of access function return access Integer;
- PhilipRoman 2y agoThe root problem is having both postfix and prefix operators (and whatever their combination is called) for types. Plus the fact that arrays in type names sometimes act as pointers and sometimes not.
- wruza 2y agoStill unclear at things like “returning pointer to array of pointer to function …”. The best shot is to name intermediate types, since they aren’t really intermediate most of the times. Properly structured programs tend to disassemble complex structures to work on them, so you need these names anyway. That said, C compilers definitely lack -Wunreadable-types flag which must be default.
- ape4 2y agoI was about to say the same thing. If you have an array of widgets used in several places in your program it would be odd not give it a name. Then you can have a pointer to it, etc.
- bluetomcat 2y agoPointers to arrays are an incredibly rare thing in real-world C programming, let alone functions returning them. They contain the same address as a pointer to the beginning of that array, with the only difference that pointer arithmetic will be done in sizeof(arr) steps, not in element-size steps. It is a widely accepted practice to typedef pointers to functions. With pointers, more than 2 levels of indirection is an indication of a badly written program. In practice, the most complex declarations you should write are "pointer to pointer to x", "array of pointers to x", "array of function pointers returning x". Stuff like "pointer to function returning pointer to pointer to array" is nonsensical most of the time.
- DrNosferatu 2y agoRare? Unless it’s people obfuscating the codebase so they are able to keep their job forever without delivering any new/real value.
- ajross 2y ago> C syntax is just horrible. All syntax is horrible. Rust is the en vogue tool of the moment and it's objectively harder to read than C. Python used to be clean and now it's sort of a mess. C++, well... Some languages try to get away with having simple syntax though. Javascript is in this camp, as of course are all the Lisps. And the result is that you end up drowning in a sea of complicated semantics instead, c.f. the famous "wat" video or the fact that every sexpr macro-ized DSL works like its own little butterfly. You can't win. C is fine. Could you do better if you started from scratch? Sure. Would it last? No.
- nicoburns 2y ago> Rust is the en vogue tool of the moment and it's objectively harder to read than C I think you're just used to C syntax. I find Rust syntax much easier to read than C syntax (and that was already true when I didn't know either language well).
- ajross 2y agoMeh. Grandparent who thinks "C is horrible" just isn't used to C. Familiarity breeds facility, that's no surprise. But no, Rust is just bad at this point given all the stuff that's been added and all the historical idioms that were pushed and then abandoned. It's not C++ bad, but it's objectively "worse" than C whose quirks all fit in a tiny K&R paperback.
- leetcrew 2y agoit's subjective to an extent, but requiring a "reverse spiral rule" (which itself has exceptions) to understand inline types is about as close to objectively bad as it gets. rust surely has it's issues, but reading types is not one of them. whatever you put inside the <> of a outermost symbol is a straightforward tree parse.
- jstimpfle 2y agoThe "reverse spiral rule" is a myth and that website should be shut down. It only adds confusion for the already confused. C type syntax has a simple guiding principle, there is no type syntax and "declaration follows usage" i.e. normal expression syntax. A few ugly additions that don't cleanly fit in this model were made later for practical reasons, but they don't change the basic idea, which is that there is so little to C type syntax that you can barely see it (which may also be why it's so easy to read once you've grokked it).
- inopinatus 2y agouggclang: error in integer literal, did you mean "ONE HUNDRED"? at line one
- jcparkyn 2y agoI was with you until the second sentence - there's so many better syntaxes C could've used but that's not one of them.
- jstimpfle 2y agoI agree it's annoying (really bad for tooling), and what's more annoying is that for a human who understands how it works, there is no syntax as easy to read and write as C declaration syntax. It's terse and intuitive, not adding new type syntax but reusing what already exists as expression syntax.
- deleted 2y ago[deleted]
- bluetomcat 2y agoTreat them like ordinary expressions around an identifier. At the deepest level is the identifer, then some operators around it with a certain precedence, and finally, on the left, a list of type specifiers indicating the final type of the expression when all the operators are applied. Those operators are the same ones you would use in an expression to use your declared object. They are the unary dereferencing operator, the array subscription operator, the function call operator and the parentheses for overcoming default precedence.
- delta_p_delta_x 2y agoIt is a bit sad that an entire blog post is required to explain type declarations in a language as common as C. It is even worse that C++ stuck with it. It more atrocious still that C decided it was good syntax to attach operator* to the name instead of the type to justify this crazy business, which thankfully C++ reversed a little with `auto` and West const. Imagine how much easier that last example would be with a trailing return type, Ptr<> and Array<> parametrised types, and lambda-esque function objects instead of function pointers: char * (*(**foo[][8])())[] versus let foo: Array<Array<Ptr<() -> Ptr<Arr<Ptr<Char>>>>, 8>> = ... It's immediately clear what the top-level type of `foo` is: some parametrised `Array`. We can do better, though. In this hypothetical language, one would probably dispense with 'pointer to char' and 'pointer to array' and replace them with view/range/span types, eliminating a layer of type parametrisation and many classes of bugs: let foo: Span<Array<Ptr<() -> Ptr<Arr<StringView>>>, 8>> = ... Though I am dumbfounded by why there needs to be a 'pointer to array' in the function return type, and a 'pointer to function pointer'. I assume these are unknown arrays themselves, which means... let foo: Span<Array<Span<() -> Span<Arr<StringView>>>, 8>> = ... A perfect language would probably have multi-dimensional spans, which would eliminate the two `Span<Array<>>` and replace with some `MultiSpan<>`. Addendum: https://cdecl.org/ https://cdecl.org/ is a very useful and very self-aware resource: 'C gibberish ↔ English'.
- neonsunset 2y agoSpan<T> is pretty much C# syntax :) (Also funny, multi-dimensional spans are coming in the form of TensorSpan<T>)
- delta_p_delta_x 2y agoIndeed. Very inspired by .NET, C++, and Rust.
- Quekid5 2y agoIn C++, you can do much of what you want with std::array, std::mdspan... and you could also create a Ptr<T> with "using" to define a type alias for T* -- see [0]. Of course the default syntax for pointers is T* and that's not going to change, so it'll be an uphill battle to keep consistent in any codebase. (I guess systematic use of a rule in clang-tidy or such could help with that.) [0] https://en.cppreference.com/w/cpp/language/type_alias https://en.cppreference.com/w/cpp/language/type_alias
- cjs_ac 2y agoRob Pike has some commentary on this in this blog post on Go's declaration syntax: https://go.dev/blog/declaration-syntax https://go.dev/blog/declaration-syntax
- ninkendo 2y ago> Go takes its cue from here, but in the interests of brevity it drops the colon This was the biggest mistake IMO. Go’s declaration syntax needs colons, it would have been 100x more readable. Compare: func foo(x int, y int) int { var sum int = x + y return sum } To: func foo(x: int, y: int): int { var sum: int = x + y return sum } The colons would have made it so much more clear to read for me.
- pjmlp 2y agoSo many things that it misses out, not only colons.
- ufo 2y agoThe lack of colons is also hard for the parser. The recursive-descent code for parsing a Go parameter declaration is quite tricky and got even trickier after they introduced generic types.
- kbolino 2y agoThe types stand out more with colons, certainly, but I don't find the former example any harder to read. It helps that types can only show up in certain places. Readability does suffer when multiple consecutive parameters use the same type and it's elided on all but the last, though. That's a quirk unique to Go that I haven't seen on any language using colons to specify types.
- lpribis 2y agoAda and Pascal are both languages which allow multiple consecutive parameters with the same type and also use `name: type` syntax. I agree, even when using Ada/Pascal with colons its difficult to mentally parse.
- jsheard 2y agoObligatory cdecl plug: https://cdecl.org/?q=int+%28*%28*foo%29%28void+%29%29%5B3%5D https://cdecl.org/?q=int+%28*%28*foo%29%28void+%29%29%5B3%5D
- TomMasz 2y agoMy (pardon the pun) goto utility back in my C programming days.
- ckocagil 2y agoUsing the clockwise spiral rule. https://c-faq.com/decl/spiral.anderson.html https://c-faq.com/decl/spiral.anderson.html
- tumult 2y agoThe spiral rule is not real and fails on even basic examples like char * foo[10][5]; The OP article shows you how to actually read the declarations, not a fake trick that will lead you astray.
- seodisparate 2y agoReminds me of https://goshdarnfunctionpointers.com/ https://goshdarnfunctionpointers.com/ which has helped in some cases.
- dailykoder 2y agoI think I've been writing C for well over 10 years now and this year was finally the one where I forced myself to remember function pointer declarations. I don't even know why it took me so long to remember such a simple thing, but possibly writing "c function pointer" into my favorite search engine and hitting enter was faster than thinking about it for a moment. But maybe also because I changed up my general programming approach a bit and try to think a bit longer before hitting up a search engine. Also forced myself to read more reference sheets than stackoverflow or chatgpt. And I must say, this made me a lot more comfortable, because I understand more. Can recommend to go through the struggle/10
- shrimp_emoji 2y agoWhen they're function parameters, you can write them as regular function declarations, btw. ;) Might hurt your memorization efforts though.
- scoutt 2y agoI thought it was just me, that after 20 years of writing C I still have to look how to declare function pointers.
- jandrese 2y agoI'm weird that I have no problem getting function pointers but have to look up the syntax to declare an enum every damn time.
- nuancebydefault 2y agoWhat i usually do is: Write a normal function declaration, prepend it with typedef and append _t to the function name. DONE The same trick works for anything you want to typedef!
- daghamm 2y agoC type declarations are quite sane, until you need function pointers. Then things get really crazy. Consider this type declaration in Go: func(string) []func(int)[]*float64 This took me 5 seconds to write. The C/C++ counterpart would probably take an hour to get right :(
- lieks 2y agoHmm... Let's see... Oh, functions can't return arrays in C. That would be a pointer. Well, that makes it easier. double **(**(*)(char *))(int) It did take a couple minutes, but I just woke up. Also, idiomatically there would be a typedef: typedef double **A(int); typedef A **B(char *); B * That took a few seconds to write.
- Arch-TK 2y agoFunction pointers are easy if you typedef the function type. What a pointer to function (int) returning int? The difficult way: int (*p)(int); The easy way: typedef int Func(int); Func *p; The typedef reads just like a normal function declaration except for the name is the type name rather than the function symbol name. Just like a function declaration, you can add more documentation by giving the parameters names. e.g.: // used for some hypothetical allocator API typedef void *Alloc(size_t size); Similar trick can be used for pointers to arrays (just typedef the array type). Although in practice pointers to arrays just make for awful code.
- Arch-TK 2y agoUnfortunately I was astounded, horrified, and simultaneously pleased to discover that I was immediately able to decipher the "hard" (huh ?????) example. Although if I was writing this myself, I would split it up like so: typedef char *StringArray[]; typedef StringArray *Func(); Func **foo[][8]; Which is equivalent and much easier to read. (Although still not trivial. I have no idea what purpose this type really serves. The type names are placeholders as a result.) C type declarations become particularly tricky specifically when people try to directly declare (or typedef) pointers to functions/arrays without an intermediate typedef for the function/array type. So if you make it a rule to typedef array types before creating pointers to them (although this usually makes for some rather unreadable code so maybe try avoiding it in general) and to typedef function types before creating pointers to them, you will usually have much more readable code. As a bonus, you can then use the function typedef to provide an early type-check for a callback function (even if it's a callback your code doesn't directly use). E.g.: typedef int Func(int a); Func foo; int foo(int a) { return a * 2; } Or, you know, switch to a higher level language.
- dvhh 2y agoWas thinking the same, but there is always this "clever" dev who insist on putting everything in one line
- mmaniac 2y agoC type declarations (and array decays, for that matter) seem to be baggage from B. In B the only type is the machine word, which can perform arithmetic and be dereferenced. Arrays the only special kind of declaration and are declared like auto foo[10] Where auto is a storage class specifier like extern or static. In that world, having declaration follow usage makes perfect sense. There's no "other" kinds of data to worry about, the operators you can apply to an expression describes its type exactly.
- WalterBright 2y agoThe array decay is probably the worst mistake C made, and was unfortunately carried over to C++. Fortunately, it is fixable: https://www.digitalmars.com/articles/C-biggest-mistake.html https://www.digitalmars.com/articles/C-biggest-mistake.html
- tialaramex 2y agoAs usual the worst mistake is Hoare's Billion Dollar Mistake (null). But yes obviously arrays shouldn't decay. The "fix" would be to not have arrays decay. I believe C++ could probably get there if it had taken Epochs, I further believe taking Epochs (P1881) when it was proposed would have been extremely disruptive but perhaps possible and worth attempting. I do not believe it's still possible, the moment passed and with it, in my view, any hope of salvaging C++. Without Epochs, any and every such change to C++ is similarly disruptive and that's too expensive so more or less nothing gets fixed, instead layers of kludges must be added. That's how Foonathan ends up with: class foo final trivially_relocatable namespace() { … };
- WalterBright 2y agoThe change I proposed is not disruptive in any way. As for the billion dollar mistake, a null pointer reference results in a seg fault. But the array decay results in buffer overflows, the #1 problem in shipped C code, and the buffer overflows result in memory corruption. Memory corruption is much, much, much worse than a seg fault. For example, buffer overflows are exploitable by malware. Seg faults are not. Hence, the array decay is the worst mistake.
- teddyh 2y ago$ sudo apt install cdecl […] $ cdecl Type `help' or `?' for help cdecl> explain char *(*(**foo[][8])())[]; declare foo as array of array 8 of pointer to pointer to function returning pointer to array of pointer to char cdecl>
- pquki4 2y agoWow, nice. Now I think every IDE should come with this...
- samatman 2y agoThought I'd take a crack at it in Zig: const foo: [_][8]**fn () [*][*:0]const u8; This is an array of result-defined size, of arrays of eight pointers to fn pointers, which returns a many-item pointer to null-terminated arrays of u8. Null terminated because we're going to assume that the C data structure is properly null-terminated... Still an insane data structure, and I'm still learning Zig, so I can't promise this is precisely equivalent to the C, I got as close as I could and am unwilling to try and compile, let alone build, such a type. No one would actually do this unless they needed `extern` compatibility. Ignoring the fact (pointed out in the Fine Article) that no one would use anything vaguely like this, a more Zig native version might be const foo: [_][8]**fn() [][]const u8 Which returns a slice of slices of immutable u8s, these being fat pointers which include their length.
- xigoi 2y agoNim equivalent: seq[array[8, ptr ptr proc(): ptr seq[ptr char]]]] I love having actual words instead of random punctuation soup.
- asimeqi 2y agoWhy do I remember The C Programming Language book by K&R explaining how to write a version of cdecl? I just checked the second edition of the book and my memory seems to be wrong.
- sea6ear 2y ago
- inopinatus 2y agoReminded of that time I tried to sneak this ternary delight into FreeBSD src: if ((Lflag ? chown : lchown)(p->fts_accpath, s->st_uid, -1)) (void)printf(" not modified: %s\n", strerror(errno)); which relies on the function prototypes of the chown(2) and lchown(2) syscalls being otherwise identical.
- kjs3 2y agoThat's demented. I don't think I'd ever think to even try that in C. chef kiss
- jandrese 2y agoNothing says "I'm up to some shenanigans" than the ? : syntax in C.
- fuzztester 2y agoExcept for nested ones; even more fun.
- mmaniac 2y agoMaybe I'm crazy or maybe I'm too used to functional programming, but I don't see anything wrong with this.
- jxy 2y agoIt's fun stuff of translating C declarations to English, given English uses its special order for possessive and verb object order. If you use Japanese, you can write the Japanese from left to write in order, while reading the C declarations from outside in. char *(*(**foo [][8])())[] then reads as charへのポインタの配列へのポインタを返す関数へのポインタへのポインタの8要素からなる配列の未定サイズの配列 which uses the reverse of English possessive, and object verb order.
- squarefoot 2y agoMany moons ago the "cdecl" tool turned out incredibly useful to check if I got declarations right. cdecl> declare bar as const pointer to array 5 of pointer to function (int) returning const pointer to char char * const (( const bar)[5])(int ) Also online at https://cdecl.org/ https://cdecl.org/
- jay-barronville 2y agoWow, I remember messing with cdecl at least a solid decade ago (if I’m not mistaken). I’m surprised the project is still active!
- eternityforest 2y agoI wonder if it would have been possible for C to have been less pointer heavy without killing performance on those old systems. Could we have done without pointer arithmetic? Could we have hidden the pointer part of function pointers and made them syntactically just like Python or JS functions? Did arrays as raw blocks of memory ever need to exist? I remember when I first learned C, I didn't really see the problem, I thought it was a perfectly fine language, until I learned Python/JS/C++/Dart and saw how nice high level features and package management is. But now I wonder how I ever used it at all without complaining!