8 ms·
It was a fun read, except when all the C bugs were listed. It's getting tiresome to hear about folks writing nominally Important software in a language where it
by reikonomusha 9y ago
It was a fun read, except when all the C bugs were listed. It's getting tiresome to hear about folks writing nominally
Important software in a language where it is extraordinarily difficult to get things absolutely correct, with paltry excuses such as "it needs to be fast" or "it needs to be portable [0] to a VAX-11/750." It doesn't give me confidence that these completely usual bugs popped up early on, and it will not surprise me when more show themselves.
The author's lack of practical familiarity with the memory hierarchy, caching, and pipelining—evidenced by his surprise by the speed gained by fetching more than a byte at a time—also makes me suspicious of his understanding of the semantics of his program. (I understand that this is a form of ad hominem as it relates to arguing the correctness of software, but it's a consideration worth noting in a broader context of trust.)
It would be great if this was written in another language.
[0] Most "portable" code written in C is not portable. Look at any mature C project and see the layers and layers of macros and hacks to make things portable.
- taneq 9y ago> Look at any mature C project and see the layers and layers of macros and hacks to make things portable. So.. it's portable, then?
- reikonomusha 9y agoRelying on a macro processor to patch and bandage your program at compile time depending on a collection of symbolic features isn't really representative of portability of a language and your code. To me, that is brute forcing portability, and is a web of conditional inclusions/exclusions that needs to be maintained and added to per-architecture and per-OS. Something that is, in some sense, truly portable is one that has a single specification that runs on all platforms the base implementation supports. Virtual machines and certain styles of language standard are often able to accomplish portability for a vast number of applications.
- SmallDeadGuy 9y agoI would argue the other way. Something which requires a base implementation to be installed on a system which generates different machine code based on a single specification is less portable than a macro processor generating different versions of a program. Sure, people will have had something like python installed since a long time ago, but a new setup requires downloading 2 objects (the base implementation and the program) compared to a single one (a platform-specific copy of a program).
- reikonomusha 9y agoYou still need to download two objects: the C code and the C compiler, even if the C code has already been processed. Edit: Not to mention libc, libm, and the like.
- SmallDeadGuy 9y agoYou don't need a C compiler to run an already compiled program. You may need libraries, just like almost every language, but only the developer needs to download the compiler and not every single user. It gives options to the developer to target multiple platforms without extra effort from the users of those platforms.
- willtim 9y agoHow is the deployment of a high-level runtime different from libc? Both can be statically-linked if required.
- majewsky 9y ago> Relying on a macro processor [...] isn't really representative of portability of a language and your code. I would argue that the C preprocessor is, for all intents and purposes, part of the C language. C is not quite practical without its preprocessor, and the preprocessor is not quite practical with any language other than C (or a derivative like C++).
- 9y ago
- willtim 9y agoPortability is not black or white. Macros are essentially selecting different code depending on the platform. A good high-level language would not need to do this.
- vasco 9y agoJust because you don't see those ugly macros to make your pure high level language portable, doesn't mean they're not there, somewhere.
- reikonomusha 9y agoYes but this is a separation of concerns. This is precisely the point of an abstraction, especially at the level of a language and its implementations. Writing code in Common Lisp means you're writing something with semantics promised by the Common Lisp specification. Vendors implement the spec (perhaps using nasty #ifdefs), and developers write code against the spec. The spec is a contract, and from the point of view of the programmer, it's indifferent to platform change and so on (up to what the spec says is platform-dependent, like machine integer size or maximum array length). Just because a particular implementation may not be considered portable, doesn't mean my program (which I may choose to run on that implementation) isn't portable.
- daemin 9y agoUnfortunately the architectural details of the machine will leak through to the program you are writing, and you will need to twist your code in order to get correct behaviour and fast execution. Case in point is the recent article posted on HN about the platform specific details of getTimeMilliseconds() in Java.
- tpxl 9y agoCould you link that article? I searched and couldn't find it on hN.
- 9y ago
- simias 9y agoWe've had to wait till C99 to get stdint.h, clearly portability is kind of an afterthought in C. C is meant to map easily to a wide range of hardware without overhead, conceptually it's almost the opposite of portability since it precludes creating standard abstractions that would hold true across architectures as those could not map to native functionality across the board. It's easy to compile C programs on a different architecture however, which is probably where this argument stems from. Remember when we switched from 32 to 64bits? How many codebases managed to make the jump painlessly?
- barrkel 9y agoIndeed; the language is portable. Programs written in the language require great effort to be themselves portable. Also, portability isn't a binary attribute. For every way two platforms vary, there are often a subset of states and transitions that are identical, and two disjoint subsets that are different. As long as your inputs start out in the identical subset and don't wander too far, you're good to go. For example, if you never put a value above 32767 into an int, you don't really care whether your int is 16 or 32 bits in size. If that int is e.g. line number in a file, it may mean that your program works fine on most files, but only the 32-bit version works correctly on large files. And of course the cleverer you are the closer you're likely to skate to the edge of the common intersection.
- int_19h 9y ago> We've had to wait till C99 to get stdint.h, clearly portability is kind of an afterthought in C. C provided "at least N bits" guarantees for its integer types since its first standard (for the curious: char at least 8-bit, short at least 16-bit, long at least 32-bit; in C99, long long as least 64-bit). This is the most that you can get if you want absolute portability, because there are architectures out there where you simply can't address memory in, say, 8-bit chunks (see SHARC for an example). Consequently, while stdint.h defines types like int32_t, they are "conditionally supported", and so a C program that has a hard dependency on them is not universally portable. Types like int32_least_t and int32_fast_t are unconditionally supported, but they don't give you anything that you didn't have before.
- thinkMOAR 9y agoCurious about the other language. What other language that doesn't add another layer between the code and system instructions were you thinking about?
- pjmlp 9y agoMost programming languages with compilers to native code (JIT/AOT), the myth of high level Assembler for C only applies if your computer is a PDP-11 or a basic 8-bit CPU like a 6502 or Z80. The ANSI C and C++ standards define the concept of abstract machine for the language semantics, just like in most languages. Additionally you have the concepts of sequence points, the new memory model semantics for multi-threaded code and the beloved UB. UB which doesn't exist in most languages, because their rather leave it implementation defined or lose the opportunity to target some strange CPU not able to support the language semantics. Also Assembly doesn't has UB, making it ironic that it is safer to write straight Assembly at the expense of portability than C or C derived languages.
- bandook_raja 9y ago> Also Assembly doesn't has UB, making it ironic that it is safer to write straight Assembly at the expense of portability than C or C derived languages. You just need to know your compiler flags/configuration to produce exact assembly output (if you want that). And about assembly not having UB, BSF and BSR are pretty good examples for that.
- pjmlp 9y agoThat just is quite ironic, because it is even version dependent across releases of the same compiler, which means anything fine tuned for version X can break on version X + 1, due to changes on the optimizer. And I doubt anyone is regularly reviewing the produced Assembly every single time they change compilers. Assembly doesn't have more than 200 documented cases that hardly any human is capable of remembering.
- TheAceOfHearts 9y agoAren't all undocumented instructions / opcodes actually UB? If you execute an undocumented instruction the processor has to do something.
- loup-vaillant 9y ago> The author's lack of practical familiarity with the memory hierarchy, caching, and pipelining Maybe I didn't express myself clearly. I knew loading stuff word by word would be much faster, thanks to my knowledge of the memory hierarchy, caching, and pipelining. What I didn't expect was the magnitude of the overall impact. The core round functions are much, much faster that I had anticipated. I didn't think the loading code, even in its slow version, would account for a significant percentage of the total running time. > It would be great if this was written in another language. Easier said than done. I aim for easy integration in projects written in basically anything: C, C++, OCaml, Rust, Python… What would you suggest? Rust? That's a possibility, but (i) it's LLVM only at the moment, and (ii) I believe there's an impedance mismatch between Rust and a C compatible ABI. I would totally recommend to rewrite the whole thing in Rust for Rust projects, though.
- arcticbull 9y agoOne of the major Rust design goals is simple C ABI compatibility, and it delivers there. That it's LLVM only appears to me irrelevant, as the generated libraries can be linked anywhere, and the specifics of how the Rust compiler are built are out of scope. So long as you get rustc up and running, what does it matter it's architecture? If you're curious, check this out: https://doc.rust-lang.org/1.5.0/book/ffi.html https://doc.rust-lang.org/1.5.0/book/ffi.html > I would totally recommend to rewrite the whole thing in Rust for Rust projects, though. Any time you rewrite anything, you run the risk of introducing new and unexpected bugs, and you're just doing unnecessary, complex work fraught with pitfalls (doubly so here) -- generally a Rust project would create a wrapper around the C code, invoke the methods and expose idiomatic APIs on top to avoid just that. This is my go-to article on re-writing things and why not to: https://www.joelonsoftware.com/2000/04/06/things-you-should-never-do-part-i/ https://www.joelonsoftware.com/2000/04/06/things-you-should-... To be clear I'm not saying you made a mis-step, just that your arguments for it don't seem at first glance to support your decision.
- loup-vaillant 9y ago> One of the major Rust design goals is simple C ABI compatibility, and it delivers there. I was just telling one has to write a wrapper either way. Idiomatic Rust is not idiomatic C, so you would need to have a "C ABI" interface in addition to the clean Rust interface. > That it's LLVM only appears to me irrelevant […] Not if it targets less platforms than GCC. Though that will likely matter less and less in the future. > So long as you get rustc up and running, what does it matter it's architecture? You have to get it up and running. You have another build step. You have to use a binary library (can't just include the source code). It's not much, but I want to maximise ease of deployment.
- seanwilson 9y ago> It was a fun read, except when all the C bugs were listed. It's getting tiresome to hear about folks writing nominally Important software in a language where it is extraordinarily difficult to get things absolutely correct, with paltry excuses such as "it needs to be fast" or "it needs to be portable [0] to a VAX-11/750." Are there any practical alternatives when that kind of portability is needed? What about transpiling to C? I know Haskell and OCaml can be made to compile their code into C code that you can then pass on to a C compiler.
- w8rbt 9y agoYes, C++. It's the only practical, safe alternative to C.
- seanwilson 9y agoIt's easier to write safer code in C++ but there's still many pitfalls and it's still a lot of effort. There's really nothing even remotely close to being practical?
- dsacco 9y agoCan you clarify what you mean by "practical"?
- wuch 9y agoIn C++, you can for example: 1. Manage resources with RAII (though, in C you could use non-portable attribute cleanup in similar manner). 2. Use type safe wrappers around builtin types like in [0] and [1]. 3. Use containers with more extensive bounds checking and iteration validity checks [1]. This could help detect some of those bugs at runtime, or even possibly prevent them from being written in the first place. Use of uninitialized value could be prevented with types that require explicit initialization or have default one (Bug 1). Left shifting a negative value could have been caught at runtime (Bug 2). Bounds check could prevent uninitialized memory read (Bug 3) Type safe wrappers could prevent an accidental promotion from being written in the first place (Bug 5). Though, you need to go out of your way to actually do all those things, not to mention that your code would integrate poorly with existing library ecosystem. IMHO choosing C++ alone doesn't improve safety of your programs compared to ones written in C all that much. [0] https://github.com/foonathan/type_safe https://github.com/foonathan/type_safe [1] https://github.com/duneroadrunner/SaferCPlusPlus https://github.com/duneroadrunner/SaferCPlusPlus
- fundabulousrIII 9y agoIt's even more tiresome hearing a new generation of programmers constantly lamenting the fact that C requires a great deal of skill to develop in and that X or Y new language completely avoids C pitfalls. Not only is it dishonest but it seems to be marketing 70% of the time. Portability does not equal 'without blemishes'. It simply means that when compiled across platforms it will perform as expected.
- adwn 9y ago> that X or Y new language completely avoids C pitfalls Yeah, you don't even need a new, hip language to avoid C's pitfalls. > Portability does not equal 'without blemishes'. It simply means that when compiled across platforms it will perform as expected. By that measure, C is not portable, since it will behave differently across multiple platforms – or multiple compilers, multiple compiler versions, even multiple optimization settings.
- fundabulousrIII 9y agoSelection and context are _so_ important, don't you think?
- adwn 9y agoThe quotes I selected do not change the context, nor do they feign a different context. Unfortunately, since you replied with nothing but a snarky comment, I do not know in what way you think I misrepresented your arguments.
- oblio 9y agoI like to think that I'm rather neutral, since I'm neither pro-C or against-C. But from a rather philosophical point of view, C is basically Portability 1.0, it was one of the first truly portable "fast" languages. I'd like to think that we could do better than C with 40 years or CS research and industry experience. Of course, there's the whole second-system syndrome so it's a wicked problem :)
- dHUSUIw 9y agoIt is getting even more tiresome to have the obligatory anti C comment every time someone implements a C library (probably in his spare time). Can you direct us to a repository where we can see your output?