13 ms·
Some things every C programmer should know about C (2002)
- williamDafoe 7y agoOne mistake in the first native C compiler was that all struct arms were in the same namespace, so struct a { struct *a next; }; struct b { struct *b next; }; was illegal because the second declaration of "next" is a redeclaration.
- jstimpfle 7y agoIf you view member access as a (global) function on a value (possibly an lvalue), then it actually makes a whole lot of sense. I find code dealing with structures that have their members prefixed with a unique prefix much easier to read, usually. Given the pervasiveness of APIs that contain struct definitions nowadays, it's also understandable that the unique-constraint was removed.
- willtim 7y agoIndeed and Haskell still has this issue today, because member access is a top-level function.
- marcosdumay 7y agoStill, it creates all kinds of problems for code generation (templates) and code by convention (as with generic). I have not seen a case of it creating problems where there weren't already plenty of other problems with the code and metaprograming had to be abandoned anyway. But it does create problems.
- int_19h 7y agoIt would make more sense if fields actually were functions, or at least function-like in syntax. Then again, OCaml still has module-scoped scoped record field names today, even though it also uses the dot syntax.
- asveikau 7y agoThis is true. It also explains why a lot of old-time Unix struct member names (including a lot that made it into POSIX or are otherwise in use today) have redundant-seeming prefixes. eg. struct stat has st_mode and not simply mode. struct sockaddr_in has sin_addr and not simply addr.
- huhtenberg 7y agoOhhhh... damn. Obviously :)
- wahern 7y agoThat's still common today for subsequent interfaces as it makes it possible to write macros for preserving API compatibility. For example, to upgrade (struct stat).st_mtime to sub-second granularity you could do: #define st_mtime st_mtimensec.tv_sec POSIX preserves many prefixes for this reason. C99 brought anonymous unions (a Plan 9 invention), which makes many of these API tricks possible without using macros.
- wahern 7y agoCorrection: C11 brought anonymous unions, which perhaps explains why they're not used in standard headers. Standard headers on most unix systems do make use of C99 features these days.
- asveikau 7y agoThanks for looking that up, I didn't know the timeframe on that feature. I've known for a long time that Microsoft headers make use of anonymous unions, which makes it a rare case where they have implemented a recent ISO C feature ahead of time. I seem to recall GCC in the same timeframe would accept this with a warning that it's nonstandard. [I guess not anymore.]
- wahern 7y agoIn addition to C11's anonymous untagged members, struct foo { union { int i; double d; }; } Visual Studio also supports anonymous tagged members, union bar { int i; double d; }; struct foo { union bar; }; Is the latter more common in Microsoft land? I've always thought the latter were more useful and wished they were standardized. Anonymous tagged fields make it possible to reuse plain struct and union definitions, permitting a simple form of inheritance; whereas with untagged fields you have to rely on macros for sharing definitions. GCC (https://gcc.gnu.org/onlinedocs/gcc/Unnamed-Fields.html https://gcc.gnu.org/onlinedocs/gcc/Unnamed-Fields.html) and clang (confirmed macOS clang-1001.0.46.4) support anonymous tagged members with -fms-extensions, but I can't bring myself to make use of it as I still try to keep many of my projects working with Sun Studio and otherwise try to avoid non-standard features.
- _kst_ 7y agoI wouldn't call it a mistake. That's how the language was defined at the time. If it was a mistake, it was a mistake in the language, not in the compiler. (Admittedly, given the lack of a standard at the time, the line between the two is less clear than it is now.)
- samatman 7y agoIn an era where line editors were a standard tool, and sed and awk were invented to do bulk changes on code, this is a defensible choice.
- jolmg 7y agoDid you mean struct a *next; or was putting the asterisk before the type name legal back then?
- zabzonk 7y ago> Bitfields may appear only as struct/union members, so there are no pointers to bitfields, arrays of bitfields You can certainly have a pointer to a struct member, so this isn't the reason why you can't have pointers to bitfields. The reason is that bits are not addressable, fullstop.
- gnode 7y agoBits are addressable, just not with a normal pointer. It would have been possible to have a special fat pointer for bits, similar to how C++ sometimes has fat pointers for member functions (depending on compiler implementation). The restriction in C can only be explained as a limitation of the language itself -- although probably motivated by the implementation complexity it would require, for a niche use case.
- deleted 7y ago[deleted]
- zabzonk 7y agoI didn't mention pointers regarding bits, I mentioned addressability - a bit cannot have an address (in any language I'm aware of), though of course you can have any number of ways of accessing it.
- gnode 7y agoPointers, as a language concept, don't have to correspond to the addressing schemes of the hardware or ISA. On some architectures instructions may only be able to address aligned whole words. Some microcontrollers (e.g. Intel MCS-51) feature bit-addressable memory. Apparently, there's a special __bit type supported by the Small Device C Compiler for using bit addressable memory on such devices, although I don't know if it has support for taking pointers to these.
- tjoff 7y agoIsn't a byte supposed to correspond to the smallest addressable unit of memory?
- netule 7y agoThis reminds me somewhat of Deep C (2011), a great read for C/C++ programmers: https://www.slideshare.net/olvemaudal/deep-c https://www.slideshare.net/olvemaudal/deep-c
- klingonopera 7y ago> Given two operands to a binary operator, find the first type in this list that matches one of the operands, then convert the other operand to that type. Does anyone know for sure whether or not the Arduino compiler deviates from this standard, or ever had, in previous versions? Does multiplication qualify as a binary operator? IIRC, I think I was compound multiplying the product of an int and a char into a long (see below), and I had to explicitly cast the char as int to get the right results, much to my surprise, as I had expected the char to get promoted to an int. But I could also have made some mistakes, it's been awhile... longType *= intType * (int)charType; EDIT: Found my old code snippet, and it kinda invalidates my original question, what I was actually doing was longType *= charType * charType * charType which leads the right side to evaluate first without promotion, thus modulating the end result by 256, which was not intended. I then cast all of them as long, but probably (certainly!...?) only one would've been enough. EDIT2: Or at least, two of them have to be casted, else it might evaluate two chars first, modulating the result, before actually promoting to long to multiply with another long. (Or not, because it promotes to the most significant data type on the right side? Ugh... this is why I prefer to be extra verbose.)
- kccqzy 7y agoThat shouldn't be necessary. The "usual arithmetic conversions" should cover that.
- ftvy 7y agoI have seen this happen with pointers on the x86_64 arch and the comparison operator where they appear to be the same value in the lower 4 bytes but the upper 4 bytes are different. I am not exactly certain, but what I think happens is that when you define your charType variable that the upper bytes in the word are still the same random data, so when you multiply without casting, it is treating the char as an int and multiplying the whole 4 bytes. When you cast, it probably just multiplies the lowest byte and carries if necessary. I am also not certain if the compiler would optimize to using a shift operation if one operand is a multiple of 2. Again, not exactly certain if this is the case, but it's similar to what I've noticed.
- muricula 7y ago
- jolmg 7y ago> Operator Precedence and Associativity There's also the operator(7) manpage for quick reference.
- azhenley 7y agoI'm surprised a bit by the "Translation Steps" part at the end. I assumed much of the steps listed happened during parsing, such as processing escape characters and converting newlines, but those are done beforehand. Perhaps it is because C uses a preprocessor and some macros would not be possible if all the steps were performed while parsing?
- nayuki 7y ago> There is an implicit "x != 0" in: if (x), while (x), ... etc. > An explicit "x != 0" in these contexts serves no semantic purpose. > And "x == 0" in these contexts might be better written as "!x". I disagree with this advice because the idea is not portable across languages. For example, if we let the variable x hold the integer value 0, then: Java 'if (x)' is a compile-time type error. Python 'if x:' is falsy, similar to C. Ruby 'if x' is truthy!
- chupa-chups 7y agoWhile this is technically correct, idiomatic javascript differs from idiomatic ruby, which (that is the point) differs from idiomatic C. So either no idioms are correct, or it is correct to apply the idioms pertaining to the language of choice.
- TickleSteve 7y agoCompletely agree... but the most serious issue is that it does not read well. Consider how you would read it: "if grass is green ..." is effectively shortened to "if grass ...". This makes no sense. I always use explicit conditions and consider "if(x)" and antipattern and have done since I was learning C back in the mid eighties.
- smcl 7y agoUnfortunately your example doesn't illustrate the point very well: if (grass_is_green) { ... } vs if (grass_is_green != 0) { ... } or if (grass_is_green == 1) { ... } The one with the implit comparison to zero is more readable
- zormino 7y agoCertain industries also have guidelines that chime in on this issue. Where I work, we have to create defines for TRUE and FALSE, and everything needs to be compared to them. Essentially making things look like if (bool_name == TRUE) There is no ambiguity, and it forces you to handle exactly equal to 1 and not equal to zero separately. if (bool_name == TRUE) and if (bit_field_value > 0) are logically different even if they can be simplified to the same thing.
- mainnode 7y agoRemember kids: Bitfields are not thread-safe!
- ajross 7y agoGotta one-up your pedantry here: kids, "atomic" and "thread-safe" are not the same thing. Bit operations are not in general atomic (though on some architectures they can be), but atomic operations themselves are only a requirement, and not sufficient for, thread safety. Thread safety is an architectural property. You can write terribly racy code using 100% atomic operations.
- zabzonk 7y agoNothing in C is thread-safe out of the box.
- saagarjha 7y agoCertain operations are, such as static variable initialization (which I believe is done during program startup in C).
- zabzonk 7y agoStatic variable initialisation that is not initialisation of such variables local to functions is performed before the program starts running, so there cannot be multiple threads. I can't remember the case in C, but in C++ the initialisation of a function local static variable is guaranteed to be thread-safe.
- int_19h 7y agoIt doesn't matter that much whether the variable is global or local, but rather what the valid initializers are. In C, all statics can only be initialized with compile-time constants. Thus, they can all be initialized before anything in the program starts running (indeed, there's no code initializing them in most implementations - they are just bytes in the data segment). In C++, initializers can be arbitrary expressions, which means that an initializer for a global variable can spawn a thread, and that thread might still be running when another global is being initialized with a different expression that is not a compile-time constant. There are no thread safety guarantees wrt those kinds of conflicts, for either globals or locals. But for static locals with runtime initializers, the evaluation of initializer is deferred until execution actually reaches the definition of that variable - and thus C++ has a special provision for thread-safe synchronization if that happens on two different threads concurrently.
- quantumfoam 7y agoC’s a dumb language that we need to rid ourselves of. Just let it go, times have changed.
- saagarjha 7y agoLetting go of C is not nearly as easy as you’d make it seem.
- kevin_thibedeau 7y agoShow me a non-C, high level bootloader for a non-trivial system.
- dodobirdlord 7y agoOreboot? https://osfc.io/uploads/talk/paper/23/Oreboot.pdf https://osfc.io/uploads/talk/paper/23/Oreboot.pdf
- pjmlp 7y agoWill the AS/400 (now IBM i) written in PL/S do? Or do you prefer the first RISC firmware written by IBM in PL.8? Then again, maybe VMS one written in BLISS.
- lone_haxx0r 7y agoI'll agree when there's a good replacement.