31 ms·
Lesser known tricks, quirks and features of C
- bluetomcat 3y agoFun fact: the order of type qualifiers (const, volatile, restrict), type specifiers (char, int, long, short, float, double, signed, unsigned) and storage-class specifiers (auto, register, static, extern, typedef) is not enforced at the current indirection level. This means that the following declarations are identical: long long int x; int long long x; long int long x; typedef int myint; int typedef myint; const char *s; char const *s; const char * const volatile restrict *ss; const char * volatile const restrict *ss;
- qsort 3y agoBut preference in ordering immediately qualifies you as coming from the east or the west const.
- dmvdoug 3y agoI like the idea of someone sitting down and looking at someone else’s code, leaning back with satisfaction after they notice the programmer’s preference. “I like the cut of their jib.”
- jfghi 3y agoI remember some really nice macro usage.
- bombcar 3y agoYou could write a Sherlock Holmesesque code review short story based on it.
- justinlloyd 3y agoI do like the cut their jib as they place their asterisks next to the variable name rather the type, just as our forefathers intended!
- schemescape 3y agoI learned a few new things, but it would be more helpful if this had info on whether these are standard and, if so, which standard they are a part of.
- thumbuddy 3y agoThis is the sprinkles on the icing of the five teir cake why C scares me. Thanks for sharing this, I'm sure it will help someone but I sincerely hope the never write C again.
- lelanthran 3y ago> This is the sprinkles on the icing of the five teir cake why C scares me. Thanks for sharing this, I'm sure it will help someone but I sincerely hope the never write C again. I looked through this list, and I gotta ask, which items exactly do you find scary? Most other popular languages have similar, if not worse, quirks than the ones in this particular list.
- xigoi 3y agoNot the OP, but: * the comma operator (why not just allow blocks to be used as expressions?) * multi-character constants (yay implementation-defined behavier) * interlacing syntactic constructs (why?????) * the fact that the --> operator works * the idx[arr] thing * the fact that you need a dirty hack with enums to check something at compile time * flat initializer lists (why?) * void pointers (instead of a proper type system) * the syntax for function types * the fact that X-Macros are needed
- lelanthran 3y ago> * multi-character constants (yay implementation-defined behavier) Isn't all of reference-implementation language like Rust and Python all implementation-defined? > * the fact that the --> operator works I pointed out elsewhere (with a link) that that specific construct works in most popular languages, like Java. > * the fact that you need a dirty hack with enums to check something at compile time It's not a check, its an assert. The alternative, for languages like Java and C#, is not having compile-time asserts at all. > * the fact that X-Macros are needed They are not needed, and indeed, equivalent functionality isn't in most popular languages (or anything but, Go, I think). Since many of your complaints are applicable to other languages, it seems, to me anyway, that what you know of C is what you've read online in popular forums.
- asicsp 3y agoThis article was already discussed here: https://news.ycombinator.com/item?id=34855331 https://news.ycombinator.com/item?id=34855331 (410 points | 4 months ago | 176 comments) But that link no longer works.
- bryancoxwell 3y ago> I did a sloppy job of gathering some of them in this post (in no particular order) with even sloppier short explanations I wonder why developers tend to be so self deprecating
- wongarsu 3y agoI assume it's just that being self-deprecating or humble correlates with many traits that make you a good developer, so people with those traits are more likely to end up in this career path and stick around in it. Just like being a sales person doesn't automatically make you overconfident, but being overconfident makes you a good sales person.
- tyre 3y agoI think it’s in part a preemptive defense against the endless nitpicking from their audience.
- camel-cdr 3y agoYeah, I think it's this. IIRC one of the first places it was posted is on the C_Programming reddit, where the bar for "lesser known" is quite high.
- badtension 3y agoBeing afraid of failure and the impostor syndrome. You mark the territory and lower the expectations to look better in the end (even if only in your own eyes). A ton of people do it, it's hard to get out of it.
- dmvdoug 3y agoThe earlier comments were so cynical I felt like I needed to offer another possibility: maybe they set out to do this in a more systematic way, then got so deep in the weeds they realized it would take them forever to put it into more systematic form, but they didn’t want to just leave it sitting there sight unseen. So they acknowledge it’s sloppier than they would like it to be, but hey, at least it’s something. That’s not really self deprecating so much as just… being transparent?
- dundarious 3y agoThe register keyword is still useful in compiler specific contexts. e.g., for GCC-ish compilers like gcc, clang: long result; register long nr __asm__("rax") = __NR_close; register long rfd __asm__("rdi") = fd; __asm__ volatile ("syscall\n" : "=a"(result) : "r"(rfd), "0"(nr) : "rcx", "r11", "memory", "cc"); The above is basically how you might implement the close(int) syscall on x86-64. You don't need to be doing embedded programming to find it useful to dip down into assembly like that (though syscalls are perhaps a bad example, even for a syscall not provided by your C library -- that library probably provides a `syscall` function/macro that does all this in a platform agnostic way). Also, "%.*" is extremely useful with strings, i.e., "%.*s". Your code base should be using length delimited strings (basically `struct S { int len; char* str; };`) throughout, in which case you can do `printf("%.*s\n", s.len, s.str);`
- messe 3y agoUm, no. You could could remove the register/__asm__("reg") qualifiers entirely and just specify "D" (rfd) as an input parameter and the code would work fine. There is absolutely no need for register there. The only good use of register these days is for project-wide globals in embedded contexts. IIRC one example of this is the decompilation of Mario 64, where a certain register ALWAYS contains the floating point value 1.0.
- dundarious 3y agoBoth are valid, but I much prefer the `register` method I gave (documented in https://gcc.gnu.org/onlinedocs/gcc/Local-Register-Variables.html https://gcc.gnu.org/onlinedocs/gcc/Local-Register-Variables....), as it is far more self-explanatory. GCC's extended asm syntax has too many inscrutable constraint and modifier codes even excluding these GCC-ext-asm-specific codes to reference machine-specific registers by name. As such, I totally disagree with your statement about "the only good use". Given that I first learned about that method of specifying registers by reading linux kernel source code, I think others would disagree as well.
- Dwedit 3y ago
- chrishill89 3y ago> Multi-character constants I asked on SO why C characters use `'` on both ends instead of just one (e.g. why not just `'a` instead of `'a'`?). This seems to have been the biggest reason.
- qsort 3y agoThe main reason for that is practicality. '\n' and '\0' are also characters. You could somehow still parse it, but it would be less clear and possibly need more escaping. Multi-character constants are historical baggage.
- chrishill89 3y agoI don’t see the issue. If it’s a literal character it’s one character; if it’s `\n` or or `\0` then it’s two; if it is an octal escape it’s four; and so on. You have to parse them the same way in a character literal as in a string literal, anyway.
- chrishill89 3y ago> if it is an octal escape it’s four; I just figured out that 1. `\0` and octal numbers share the same prefix 2. Octal numbers can have 1-3 digits (not fixed) So maybe it’s more tricky than I thought.
- _kst_ 3y agoAn octal-escape-sequence is a backslash followed by 1, 2, or 3 octal digits. '\0' is just another octal escape sequence, not a special-case syntax for the null character. "\0", "\00", and "\000" all represent the same value; "\0000" is a string literal containing a null character followed by the digit '0'. Hexadecimal escape sequences can be arbitrarily long. If you need a string containing character 0x12 followed by the digit '3', you can write "\x12" "3".
- chrishill89 3y ago
- projektfu 3y agoI used to use the comma operator to return a status code in the same line as an operation, but for some reason nobody liked my style. if (error_condition) return *result=0, ERR_CODE; So, back to writing lots of statements.
- enriquto 3y agoI love this style (avoiding multiple-statement blocks) ! Sometimes you can still avoid multiple statements by rearranging your code otherwise. For example, in your case, you can set *result=0 at the beginning of the function. Other times, you can also cram the assignment inside the condition using short circuit evaluation; this trick somehow seems more palatable to normies than the comma operator.
- projektfu 3y agoYeah, I felt that in cases (like when doing COM programming) where the true result is almost always returned in a parameter and the status code as the return value, it made a lot of sense to me to combine those into one line wherever they appeared. But, like the article says, this is a lesser-known operator. In libc style, a similar thing makes sense, e.g. return errno=ENOENT, (FILE*)0; I don't know if anyone uses this style. In K&R, they say that it's mostly used in for loops, such as for (i = 0, j = strlen(s) - 1; i < j; i++, j--) ... So that is where I use it now.
- gpderetta 3y agoI have used it c++ to take a scoped lock without naming it and returning a mutex protected value: return scoped_lock{foo_mux_}, return foo_; Also nobody likes it.
- Croftengea 3y ago> Compound literals are lvalues > ((struct Foo){}).x = 4; Do such lvalues have any real use?
- kzrdude 3y agoYou could pass them to a function where something non-null is required but you don't want to use it, like : `f(&(struct Foo){0})`
- deleted 3y ago[deleted]
- eqvinox 3y agoThe key thing about it being an lvalue is that you can take its address — you can only take the address of lvalues. Other than that, no, no real use.
- EPWN3D 3y agoCasting to and from void is a "lesser known" feature of C?
- wruza 3y agoAlso “lesser known” means “not in C FAQ” to me. Half or more of TFA is a pretty regular C FAQ material that everyone C already has read. I mean, sure, right?
- defrost 3y agoPretty much. Although even things in the C FAQ get overlooked and forgotten. eg: a 'Day of the Week' one liner such as dow = (y + y/4 - y/100 + y/400 + m["-bed=pen+mad."] + d) % 7; with int day (d), month (m), year (y) inputs and single int day of week output has a slight twist that causes a few head scratches for some.
- zalyalov 3y agoThat... that way to handle leap years can't possibly be right, can it? It doesn't account that you don't need to add the leap day of the current year if the day is before Feb 29
- defrost 3y agoErr, my bad, I left out the prequel (still a one liner) y -= m < 3; dow = as above, etc; I was focused on an 'obscure' yet fundemental part of the C language pointer | array equivalence. Addendum: https://c-faq.com/misc/zeller.html https://c-faq.com/misc/zeller.html
- jnspts 3y agoC11 added _Generic to language, but turns out metaprogramming by inhumanely abusing the preporcessor is possible even in pure C99: meet Metalang99 library. I'm actually working on a library doing just that! It's still in very (very) early development, but maybe someone may find it to be interesting. [1] Link [2] is the implementation of a vector. Link [3] is a test file implementing a vector of strings. [1]: https://github.com/jenspots/libwheel https://github.com/jenspots/libwheel [2]: https://github.com/jenspots/libwheel/blob/main/include/wheel/vec/impl.h https://github.com/jenspots/libwheel/blob/main/include/wheel... [3]: https://github.com/jenspots/libwheel/blob/main/tests/impl/string.h https://github.com/jenspots/libwheel/blob/main/tests/impl/st...
- burstmode 3y agoC++ Metaprogramming is also just a bunch sugarcoated preprocessor macros and it was never someting else.
- idispatch 3y agoThis is plainly not true
- WalterBright 3y agoThe fundamental difference is the C preprocessor is actually a language independent of C. They follow their own grammars, syntax, semantics, symbol tables, etc., and do not communicate with each other.
- cempaka 3y agoThe downvotes make me laugh, I did something pretty similar not long after the _Generic keyword came out and remember getting a pretty icy reception even though I was pretty up front about how painful and crufty it is. https://abissell.com/2014/01/16/c11s-_generic-keyword-macro-applications-and-performance-impacts/ https://abissell.com/2014/01/16/c11s-_generic-keyword-macro-...
- eqvinox 3y agoThe %.* example is so close to hitting its single most useful application: char *something; /* no null termination */ size_t something_length; printf("%.*s", (int)something_length, something); Unfortunately, the .* argument has type (int), not size_t, and it's signed… but if that's not a problem this is a great way to format non-\0-terminated strings. (And of course you can also use it to print a substring without copying it around first.)
- jrpelkonen 3y agoIn this simple case, if the int cast is a problem, fwrite would be an adequate alternative, don’t you think?
- PaulDavisThe1st 3y agonot for s(n)printf ...
- LegionMammal978 3y agoFor that, the analogue would be memcpy; but both alternatives lose the ease of surrounding the string with other text, since you either have to do the length calculations or define helper functions.
- eqvinox 3y agoThe point of this is that you can use it in format strings where you may be printing a whole bunch of other things. In particular there are some APIs where you need to submit the whole format/string in one go, an example from libc is syslog(), but other libraries do this as well occasionally.
- thaliaarchi 3y agoSomewhat related to this, printf alone in a loop is Turing-complete, by using %-directives like that. It was introduced in “Control-Flow Bending: On the Effectiveness of Control-Flow Integrity” (Carlini, et al. 2015) and the authors have implemented Brainfuck and an obfuscated tic-tac-toe with it. [0]: https://nebelwelt.net/publications/files/15SEC.pdf https://nebelwelt.net/publications/files/15SEC.pdf [1]: https://github.com/HexHive/printbf https://github.com/HexHive/printbf [2]: https://github.com/carlini/printf-tac-toe https://github.com/carlini/printf-tac-toe
- deleted 3y ago[deleted]
- buserror 3y agoI "abuse" unions and anonymous unions all the time, it's very practical, and make the 'user' code a lot clearer as you can access the small 'namespace' as convenient. Here for example I can access it as named coordinates, x,y points or a vector. typedef union c2_rect_t { struct { c2_pt_t tl, br; }; c2_coord_t v[4]; struct { c2_coord_t l,t,r,b; }; } c2_rect_t;
- trentnelson 3y ago100% agree! I use whatever tool in the C toolbox results in the easiest-to-(read|grok) code, which means tons of anonymous union/struct “abuse”, bit fields, function pointer typedefs, strictly adhering to “Cutler Normal Form”.
- bruce343434 3y agoWhat is "Cutler Normal Form"?
- synergy20 3y agoconfused by the code,can you elaborate more
- thaliaarchi 3y agoThe trick for preserving array lengths in function signatures looks quite useful (e.g., `void foo(int p[static 1]);` for a non-null pointer p). Unfortunately, I think the overloaded use of `const` and `static` somewhat obfuscates its semantics.
- omoikane 3y ago%n format specifier was lesser known until an IOCCC winner made it famous. https://news.ycombinator.com/item?id=23445546 https://news.ycombinator.com/item?id=23445546 - Tic-Tac-Toe in a single call to printf
- s_gourichon 3y ago* Context: const and compile-time constants Constructs fully determined at compile time have some benefits. But const in C is weaker than constexpr that C++ has. As prog-fh summarizes on https://stackoverflow.com/questions/66144082/why-does-gcc-clang-handle-code-slightly-differently-example-given https://stackoverflow.com/questions/66144082/why-does-gcc-cl... > "The const means « I swear that I won't change the value of this variable » (or the compiler will remind me!). But it does not mean that it could not change by another mean I don't see in this compilation unit; thus this is not exactly a constant." One example of invalid C: const int mysize = 2; const int myarray[mysize]; gcc: error: variably modified ‘myarray’ at file scope clang: warning: variable length array folded to constant array as an extension [-Wgnu-folding-constant] const int myarray[mysize]; * Good news: C can do compile time constant structs and array with deep self-references. Yes, in C you can define and fully declare complex data structures that are accepted as compile-time constants, including pointers to parts of itself. See "self-contained, statically allocated, totally const data structure with backward and forward references (pointers)?" for a previous example at https://stackoverflow.com/questions/47037701/can-c-syntax-define-a-self-contained-statically-allocated-totally-const-data-s https://stackoverflow.com/questions/47037701/can-c-syntax-de... ----------------- I used this for a game on a retro machine where such a data structure avoids code which would have been several times (perhaps 10 times) bigger: https://github.com/cpcitor/color-flood-for-amstrad-cpc/blob/master/widget_configscreen.c https://github.com/cpcitor/color-flood-for-amstrad-cpc/blob/... Here's another take showing two variants: where overall construct is an array then a struct: https://gist.github.com/fidergo-stephane-gourichon/792c194e1b051a70995c27b5af8df92d https://gist.github.com/fidergo-stephane-gourichon/792c194e1...