24 ms·
C Is Not a Low-level Language (2018)
- usrnm 3y agoOf course it isn't, but what's the alternative?
- lproven 3y agoMy favourite article about C in years. To answer your question off the top of my head, answering different bits of the issue, from the perspective of the era of active programming language R&D not themes on themes on themes as we have now... Limbo, Occam (Occam-pi, etc.), APL (I/J, Aplus, etc.), Oberon (Oberon 2, Oberon 07, Active Oberon, Zennon)...
- usrnm 3y agoI'm not familiar with these languages, but which of them is closer to the actual modern hardware than C, while still being abstract enough to be portable?
- lproven 3y agoIn what way did I imply that any of them were in any way closer? That was not my intention at all. You asked what alternatives there were. C is a systems implementation language, designed to be compiled to object code that will run on the bare metal. I offered some examples of alternatives to that role, as I thought you asked. I did say that they explored different aspects of the problem. As I said to someone else upthread: It does not need to be a relative statement in order to be correct. The statement "C is not close to the instruction set of a modern CPU" does not need to be validated by specifying examples of languages that are closer.
- taeric 3y agoI think it is somewhat fair to take "what is the alternative" to be in relation to the headline claim. So, "what is the alternative that is low level?" I think I agree that it is fair to push back on the very idea of a "low level" language. But that feels somewhat banal. We get it, there are abstractions even at the lower levels nowadays that simply didn't exist back when. Similarly, if someone claims that Haskel isn't a "high level" language, what does that mean? And to be fair, we have screwed up terms so much it is embarrassing. I see arguments on whether or not LISP is a functional language quite often. There was an amusing discourse not long ago on whether SQL was declarative. Turns out, taxonomies are tough and strict taxonomies are near useless.
- lproven 3y agoIt's not my headline claim, and if one wants to enter into debate about what the article means, then step 1 is reading the article. As for the rest: well, yes, fair enough, but somewhat tangential to this subthread, I feel.
- taeric 3y agoI meant my first claim to be that it is charity to the question that it was written to reflect against the headline. Even with what comes in the article. Which, frankly, doesn't change much? Indeed, the core claim at the start of the article is "The features that led to these vulnerabilities, along with several others, were added to let C programmers continue to believe they were programming in a low-level language, when this hasn't been the case for decades." But, no they weren't. They were added to allow the CPUs to maintain resource utilization while executing code that they are taking a probabilistic stab at. There is some odd appeal to GPU programming, ignoring that the main reason GPU programming can do what they do, is because it is a foregone assumption that you will have to do the same operation across the entire visible scene. So, back to the question at hand, what is this "lower level language" that is being talked about? Best I can see from this article, it is "c" but with vectors and no aliasing? And many more core instructions? I know of basically no languages that make it clear that sqrt could be a CPU level instruction. And that one is somewhat trivial to name. It wasn't too long ago that we saw discussion of popcnt instruction. Is that a "primitive" part of any non-assembly language? It is a neat assumption to challenge, that C may be limiting what we can do. But, with how often the C and C with inline assembly dominate most any performance category, it is a steep hill to climb to show that that is what is limiting us. I also find the closing remarks about how "There is a common myth in software development that parallel programming is hard" to be kind of flippantly insulting. Would be like claiming any sport is easy because "look, you can teach grade school kids to play." Especially as I have seen plenty of bugs in actor-model languages to know it is no panacea. I agree it is easy to specify parallel activities. It gets a lot harder as you start adding in all of the deadlines and other work handoffs that are necessary for fast execution. Again, sports make a good example. Hitting a ball is easy. Running cross court to return a fast shot from an opponent is, essentially, the same thing. Far far harder, though.
- agumonkey 3y agoWhile I'm reading about limbo and occam, what do you think apl and oberon can express that C cannot ? talking about low level electronics benefits (apl array idioms are superb for sure)
- pjmlp 3y agoBounds checking by default. Actors, more precisely active objects in Active Oberon, the only one still actively being developed at ETHZ from Oberon linage.
- lelanthran 3y ago>> what do you think apl and oberon can express that C cannot ? > Bounds checking by default. That's odd - I've written a C container library that checks bounds by default. Are you sure that C doesn't allow you to check bounds?
- pjmlp 3y agoAbsolutely unless you're using a compiler with language extensions for pointer management. A library isn't the language that is described by the ISO C standard document.
- lelanthran 3y ago> A library isn't the language that is described by the ISO C standard document. Sure, but the poster didn't ask "what comes with apl and oberon that doesn't come with C", they asked "what do you think apl and oberon can express that C cannot?" And you absolutely, positively can EXPRESS bounds checking in C. I'm not sure where you heard that this is impossible, but it's probable you misunderstood or that source is wrong.
- pjmlp 3y agoFirst of all your library doesn't come with C, otherwise it would be defined on the PDF I can buy from ISO. Secondly using if statements and conditional expressions isn't what bounds checking in a programing language is about. Here is some education material, https://en.wikipedia.org/wiki/Bounds_checking https://en.wikipedia.org/wiki/Bounds_checking > Many programming languages, such as C, never perform automatic bounds checking to raise speed. However, this leaves many off-by-one errors and buffer overflows uncaught. Many programmers believe these languages sacrifice too much for rapid execution.[1] In his 1980 Turing Award lecture, C. A. R. Hoare described his experience in the design of ALGOL 60, a language that included bounds checking, saying: Feel free to update the Wikipedia page and convince Wikipedia of your reasoning.
- ahoka 3y agoGNU assembler, nasm, if you really need to go low level, usually you don’t.
- papruapap 3y agoisnt assembler a high level language nowadays?
- d_tr 3y agoWhy would you think so? Assembler is what it has always been, i.e., mnemonics for machine instructions. Unless you are thinking about microcode, which is nothing new and I am not sure it should count as a "level" from the perspective of a programmer anyway.
- semiquaver 3y agoBy the standards of this article assembler on most architectures suffers from many of the same things that make C “not low level”: in particular it offers little control over cache hierarchy or coherency (modulo hint instructions like x86’s PREFETCH), nor instruction level parallelism. Of course, these aspects are entirely due to the fact that the dominant lingua-franca (C) has no ability to support these semantics. In large part the article argues that in most cases the abstract machines that ISAs describe differ so fundamentally from the reality of how code is executed on the underlying machine to make a truly low level language impossible to achieve.
- illys 3y agoYour question seems provocative... but that's a very good question. I've always liked assembly programming and I got very puzzled when I discovered the processor metal have gone very far from the x86 instruction set I was writing. Il felt like the magic was gone. Indeed there is no direct match anymore between instructions and gate combinations on the processor die. There is a microcode translating x86 instruction into whatever electronics are below. Change this microcode, and you could have your processor speak a different binary code (matched to a assembler language).
- rfoo 3y agoRewrite the world in Rust /s The real answer is: none. There are two problems, the first is you have to rewrite the world with the new language and hardware. The second is, unfortunately, language enthusiasts who are willing to rewrite the world AND can get job done want a language to target a sequential abstract machine (i.e. look like C).
- pjmlp 3y agoC++ is serving me well staying away from C as much as possible, since 1993.
- olafura 3y agoOne example given in the article is Erlang VM which maps a lot better to modern processors. We currently have a problem where we can't have thousands of cores because, even today, so much code is designed to be fast on one core. We really have to move the asynchronous programming because synchronizing async hardware is both complex and inefficient. RISC V is probably going to help since it allows for a lot of experimentation.
- creshal 3y agoThe article does mention a few areas of interest: - Languages with "better" (=more modern hardware friendly) loop constraints are easier to parallelize (Fortran, Erlang, …) - CPU architectures with better programmable vectorization (ARM SVE, Risc-V VE) are much easier to work with, if the language primitives allow it (see above) Porting software over to fortran/erlang on aarch64 is something you can already do today, if you want to. Rust/Zig/etc. and RISC-V could have a good opportunity here to figure out better ergonomics for vectorization and more hardware friendly cache coherency policies, too, but no clue if anyone in the relevant standard gremiums cares. In terms out "but what can I easily use as drop-in replacement?" Yeah, we're kinda stuck with C and languages that inherit its problems (current Rust/Zig/etc. included).
- xet7 3y agoRust/Zig does not have enough portability, there is errors trying to compile to s390x: https://github.com/wekan/wekan-node20#trying-to-compile-llvm-for-zig-and-bun-at-s390x-fails https://github.com/wekan/wekan-node20#trying-to-compile-llvm... C89 compiles to 30+ CPU/OS: https://github.com/xet7/darkesthour https://github.com/xet7/darkesthour
- somat 3y agoI don't think it was ever claimed that C was a low level language. In fact I have always heard it as the canonical reference for an example of a high level language. I will admit that in this day and age C feels like a low level language. Lower level is something that maps more directly to machine operation (assembly, maybe forth). Higher level is something that has it's own semantics of operation and need to be converted to into the machine operation, the more conversion the higher the level.
- woodruffw 3y agoIt is somewhat common to describe C as “low level” in introductory programming or CS classes (before the student would know what an abstract machine is). Lots of people carry misunderstandings from that early simplification forwards in their careers, especially if their only interaction with C is academic and not professional.
- philipov 3y agoWhen C was declared to be "high level", there did not exist a level higher than C. Now that there are more levels above C, it is not the highest level language out there, which makes it low level compared to them. It is not the lowest level, but it's not a misunderstanding to call it low level. The playing field is not the same as it was 50 years ago, so relative terms like "low" and "high" have naturally changed referends.
- coldtea 3y agoHuh? Tons of languages at levels "higher than C" existed at that time C was created, and they were popular too. LISP (1960), Smalltalk (1972), BASIC (1963), FORTRAN (1957), COBOL (1959) and countless others. Heck, ALGOL (1958, 1968) was much higher level than C too.
- pjmlp 3y agoYes it did, outside Bell Labs.
- noirscape 3y ago
- Gazoo101 3y agoI think this statement at the end of the article - 'There is a common myth in software development that parallel programming is hard.' - is misleading. Granted the author denotes explicit situations where it is not hard, but if it's applicable in general, then it is hard. Not a common myth. Is parallel programming hard? Without any further details or specifics, yes it is. It is far harder to conceptualize code instructions executing simultaneously, than one-at-a-time in a sequential order.
- ethbr1 3y agoAgreed. The potential state space in parallel processing is a lot larger, which makes it more complex, which makes it harder. That Erlang exists and people use it successfully does not mean that harder things aren't.
- unblough 3y ago> Is parallel programming hard? Without any further details or specifics, yes it is. It is far harder to conceptualize code instructions executing simultaneously, than one-at-a-time in a sequential order. If I program (map inc [0 1 2 3]) is it really any more difficult to conceptualize the (inc ) function performing on each element sequentially than in parallel? I think the difficulty of parallel programming is less innate and more two fold: 1) languages often default to sequential so to do async requires introducing additional primitives to the programmer 2) knowing when to effectively use parallel programming When I have a list or stream that I know has independent elements that require wholly independent calculations then parallel programming is straightforward Where people get hung up is trying to shoe horn async where it is either unnecessary (performance is equal or worse than sequential) or introduces breaking behavior (the computations are in fact interdependent).
- mgaunard 3y agoMost problems are not embarrassingly parallel. (Fun fact: I once had someone call HR on me because they didn't know embarrassingly parallel was a technical term, and they thought I was belittling them)
- jansan 3y agoIf you have never heard of the PDP-11 before like I did until yesterday (I should probably be banned for this from HN), this is really something worth learning about. There is an awesome project for a PDP-11 front panel replica running an emulator on a Rhaspberyy PI (the whole thing is called PiDP-11, haha). Here is more information: https://obsolescence.wixsite.com/obsolescence/pidp-11 https://obsolescence.wixsite.com/obsolescence/pidp-11
- fsniper 3y agoMandatory xkcd: https://xkcd.com/1053/ https://xkcd.com/1053/
- wolframhempel 3y agoI feel, low to high level is a spectrum, not a binary. C is arguably in the lowest third of languages, exposing you to a lot of machine primitives like memory and thread management. It may not be as low level as assembly, but it is arguably lower level than Java or Go, and definitely nowhere near the Pythons and JS of this world.
- Joker_vD 3y ago> exposing you to a lot of machine primitives like memory and thread management Except it doesn't really, the standard leaves most of the really machine-dependent parts undefined; only very few things are left implementation-defined. Plus, of course, C is quite unsuitable for any platform that uses segmented memory/non-flat addresses (which are things that are trying to come back in vogue but C's wide spread really, really hinders that).
- mytailorisrich 3y ago> Except it doesn't really, the standard leaves most of the really machine-dependent parts undefined Well that's because it is low level and, especially, simple, and doesn't try to abstract things.
- TheOtherHobbes 3y agoIt's a certain kind of low level - specifically a PDP-11 kind of low level. If your hardware is significantly different, it only looks low level. In reality plenty of mapping and conversion goes on behind the scenes - sometimes with hilarious consequences.
- pornel 3y ago> and doesn't try to abstract things. The C standard is a description of an abstract machine. You get UB and unexpected miscompilations, because the optimizer is not evaluating how your code runs on the machine you're compiling for, but simulates running your code on the weirdly abstract C machine, one that can't overflow signed integers. And C abstracts away almost everything about stack, stack frames, and all the complexities of memory and cache hierarchies. They are abstracted to be uniform linear address space.
- Waterluvian 3y agoMy sense is that this is really a communication issue (when is it not?) On a relative scale, C is very low level compared to how we program today if you think about levels of abstraction. If “low level” means “runs on the CPU almost literally as written.” then no it’s not.
- fweimer 3y agoIt's more than that. C-the-language just doesn't have low-level concepts such as machine addresses, and its facilities for dealing with the types that the abstract machine ascribes to all objects are quite limited. Ada has System.Address to model machine addresses: http://ada-auth.org/standards/rm12_w_tc1/html/RM-13-7.html#p34 http://ada-auth.org/standards/rm12_w_tc1/html/RM-13-7.html#p... C++ has std::less specializations for pointer types which provide a strict total order (one aspect of machine addresses): https://en.cppreference.com/w/cpp/utility/functional/less https://en.cppreference.com/w/cpp/utility/functional/less There is also placement new and std::launder for more explicit control of typed memory: https://en.cppreference.com/w/cpp/language/new https://en.cppreference.com/w/cpp/language/new https://en.cppreference.com/w/cpp/utility/launder https://en.cppreference.com/w/cpp/utility/launder These days, even Java tries to model machine addresses: https://docs.oracle.com/en/java/javase/21/core/foreign-function-and-memory-api.html https://docs.oracle.com/en/java/javase/21/core/foreign-funct...
- kortex 3y agoYep, this is a linguistic problem, not a technical one. "C is not a low level language" implies that the hi/lo boundary lies below C. What's below C? IR, Asm, and opcodes. IRs like LLVMIR and various bytecodes. Well, those don't map to the hardware 1:1, not even close. So IR must be HLL. Sure Asm has to be architecture specific, but even then we are getting pretty good at transpilation. And those codes get translated to opcodes anyways on most modern chips. Basically, unless you are assembling on an ancient system or embedded processor, you aren't writing in a "low level language". Very few folks nowadays do this, so the term "LLL" doesn't occupy much mindshare in semantic space. That leads folks to populate it with what they perceive as low level - the lowest language on the abstraction tree they are likely to encounter - C. This divide is only going to expand so I say we just accept the definition of low level language has shifted, and call anything where it does closely match... something else, I don't have a good term. Maybe "hardware level language".
- acqq 3y ago(2018)
- BeefyMcGhee 3y ago(2018)
- throwaway87543 3y agoOther than assembly, which barely qualifies as a language, what programming language is lower than C?
- casparvitch 3y agoForth/joy maybe?
- drsopp 3y agoB
- Joker_vD 3y agoOr T3X9, if you're prefer Algol-style syntax.
- deleted 3y ago[deleted]
- dist1ll 3y agoIn general terms, any language aiming to be lower-level than C should - have an "abstract" machine that is more concrete than C (and by extension less portable) - be easier to lower into optimal assembly (especially loop ops) - give you strong and precise compile-time guarantees about memory layout (padding, bitfields), variable sizes, register spilling, stack usage, etc.
- pjmlp 3y agoPlenty to chose from since 1958's introduction of JOVIAL, when one cares to research what has happened in the world of systems programming outside Bell Labs, and UNIX/C taking over the server room.
- humanrebar 3y agoLLVM IR
- Nzen 3y agoI think there is an argument that Brainfuck [0], et al, is lower than C, given that it eschews variables and functions. [0] https://esolangs.org/wiki/Brainfuck https://esolangs.org/wiki/Brainfuck
- pizlonator 3y agoFriendly local C programmer and compiler writer here to remind you that C definitely is a low level language for those who understand it and use it professionally. If you’re looking for a low level language, then C (and its relatives) are your best bet. If you’re new to the language and want to understand how to use it like a pro then ignore this post - it will only confuse you and reduce your ability to use C effectively.
- pjmlp 3y agoOnly when taking into account language extensions that are compiler specific and not part of ISO C. Also a reminder that any language can have toolchains with extensions exposing low level features.
- deleted 3y ago[deleted]
- hardware2win 3y ago>use it professionally I think this post goes way way way above boringness of day2day jobs. Yea, this post is not about how to use hammer, but more like curious consideration whether using hammers everywhere is not limiting us (C design)
- lelanthran 3y ago> Yea, this post is not about how to use hammer, but more like curious consideration whether using hammers everywhere is not limiting us (C design) Maybe it [EDIT: the post] is, but the title is obviously nowhere near accurate - if C is not a portable low-level language, what on earth is? [1] It gets reposted everywhere so often I have read it multiple times, and the one thing in common I see is how every know-it-all crawls out of the woodwork to comment on the title, as if the title was something new, deep, profound or even correct.
- rfoo 3y agoThe post argues that there is no portable low-level languages, including C. i.e. truly low-level languages can't be portable and is bound to the architecture.
- thatjoeoverthr 3y agoReminds me of VLIW. As per Wikipedia, from the Itanium page: > One VLIW instruction word can contain several independent instructions, which can be executed in parallel without having to evaluate them for independence. A compiler must attempt to find valid combinations of instructions that can be executed at the same time, effectively performing the instruction scheduling that conventional superscalar processors must do in hardware at runtime. If your CPU exposed the single-stream parallelism at the interface, you can do it at compile-time or even decide it with in-line assembler. I wonder if it hasn't caught due strictly to the business dynamics of the industry, or are there technical reasons this isn't really a good strategy?
- thatjoeoverthr 3y agoIm reading that TeraScale (AMD) works this way. Itanium is a major attempt to ship it in a CPU. I guess AMD64 and ARM rule the day but maybe in the future we'll see it again.
- varelse 3y ago[dead]
- JonChesterfield 3y agoTerascale was a vliw, worked well as far as I know. The current amdgpu architectures aren't - those are multiple execution port systems, reminiscent of the x64 setup. Qualcomms' Hexagon is a vliw, I think that's contemporary. Graphcore's IPU is two instructions per word.
- lizknope 3y agoAre you asking why VLIW hasn't caught on? There are DSPs that use VLIW concepts. But for general purpose computing look at Itanium and it's failure.
- Joker_vD 3y agoWell, IIRC it didn't caught on mostly because of a) compilers weren't really that good at that kind of instruction scheduling (and when they improved, Itanium has sunk already), b) conventional ISAs (that is, x86) got quite good at doing this in hardware, at runtime, and actually deliver slightly better results than static scheduling precisely because they do it at runtime, when profiling data is available. I believe Linus has a good even if tangentially related to this exact topic rant at [0]. "While the RISC people were off trying to optimize their compilers to generate loops that used all 32 registers efficiently, the x86 implementors instead made the chip run fast on varied loads and used tons of register renaming hardware (and looking at _memory_ renaming too)." [0] https://yarchive.net/comp/linux/x86.html https://yarchive.net/comp/linux/x86.html
- draw_down 3y ago[dead]
- abainbridge 3y agoThis article is correct that your computer is not a fast PDP-11 but wrong that this has anything to do with C. Eg, "another core part of the C abstract machine's memory model: flat memory. This hasn't been true for more than two decades." This has nothing to do with C. The hardware insists on this abstraction. And its a good job too, otherwise your programs would stop working when moved to a machine with different cache.
- semiquaver 3y agoThe article argues that the hardware’s insistence on this abstraction is in large part _because_ of C’s dominance.
- mgaunard 3y agoIf only that were true. Lots of languages that have nothing to do with C also did it. It's just much easier to program with a unified memory model, that's all there is to it.
- creshal 3y agoMany of those languages indirectly have lots to do with C – even if you ignore the obvious problems like "C is the only ABI supported by most OSes, C FFI is the only cross-language interface supported by most languages and thus most libraries", there's more subtle influenced: Copying e.g. the (very expensive to implement in hardware) cache semantics of C usually "costs" languages nothing, because the hardware is already there, due to C. Not copying them happens, if both language and hardware get developed at the same time, but it's much rarer. You see similar problems with things like vectorization – Rust was in a good position to define semantics more amenable to ARM SVE / Risc-V VE, but all existing SIMD libraries are written for C and x86 semantics, so that's what Rust is currently stuck with, as are most other languages.
- saagarjha 3y agoWhat “C semantics” are baked into Rust’s SIMD libraries?
- i_am_a_peasant 3y ago[dead]
- bee_rider 3y agoI think the title that the authors decided to give this article was unnecessarily provocative in a distracting manner. I’m pretty sure there is a technical definition of low level language they are referencing that excludes C, and pretty much only includes assembly as a low level language. Ok, fine, whatever. Their bigger point seems to be that C is no longer very mechanically sympathetic to huge modern cores, because the abstraction pretends there’s only one instruction in flight at a time. Is anyone aware of a language that fits the hardware better? Maybe Intel needs to release a “CUDA of CPUs” type language.
- ori_b 3y agoIt doesn't even include assembly.
- eigenform 3y agoEven before that, this is ultimately about that fact that an ISA for a general-purpose computer can be seen as a way to abstract away parallelism. Even in your favorite assembly language, the effects are largely supposed to happen one after another. That abstraction is leaky, but the alternative is VLIW machines - even in that case, you probably end up using a compiler so that you don't have to worry about parallelism. Reasoning about parallel things is hard, that's why we spend so much time trying to avoid it ¯\_(ツ)_/¯
- karmakaze 3y agoThis is a great article (worth reading if interested in performance/parallel computing) but the complications it gets into are mostly in the CPU architecture/hardware to which compilers add additional complexity. Even without the compiler optimizations there's still branch prediction and associated parallel execution of serial machine code. To anyone debating whether C is low/not-low level language note that this discussion is at a much lower level so 'low' has a lower than common meaning.
- bluGill 3y agoAssembly is not the lowest level language you can work in. I've programmed in raw binary opcodes before, that is the lowest level. (though there is a valid argument that microcode is even lower level - I disagree but still acknowledge the argument is valid) Often a single assembly language instruction can be one of more than 30 different opcodes as registers are often encoded in the opcode. Of course at this level you have to have your CPU instruction manual as they are all different.
- toinewx 3y agoagreed
- jokoon 3y agoI once read that C is the new assembly, because all CPU have a C compiler. I then decided to make a language that compiles to C, it's just about adding strings, list and tuple. I almost finished the parser and the "translator" will take more time (I encourage anybody to try lexy as a parser combinator). Basically it will use a lot of the C semantics and even give C compiler errors, so it will save me a lot of work. Of course I am very scared that I will run into awful problems, but that will be fun anyways.
- lebuffon 3y agoEveryone knows that CPU stands for "C" processing Unit... :-)
- mgaunard 3y agoWith the same argument you could even argue that the x86 ISA is a high-level language, since under the hood it's decomposed to micro-ops which are scheduled on a superscalar infrastructure and run out of order.
- ori_b 3y agoIf you accept the premise of the article, you also need to accept that assembly is not a low level language, and that it is impossible to program any CPU currently for sale in a low level language. The abstraction CPUs give you is more or less a fast pdp11 with some vector registers bolted on. The implementation internally is not.
- variadix 3y agoThis is pretty much my issue with this article. By its criteria assembly isn’t low level, which makes the claim pretty uninteresting. You could argue there are/were processors that had low level ISAs (VLIW, no OoO exec, no memory reordering, no caches, no branch prediction, etc.) but they are all niche (usually low power embedded DSPs) or failed to capture the market (Itanium) because they failed to deliver performance comparable to the highly abstracted CPU supposedly “designed” to run C (also a questionable claim, I think the reality is it has more to do with sequential execution being the way humans think, a point Jim Keller has made in several interviews).
- HarHarVeryFunny 3y agoIf the sophistication of modern CPUs makes C no longer a "low level" language, then the same applies to assembly language .. things like out of order execution and register naming applies there too. I guess the sophistication of compilers in recent decades adds to the argument since even the assembler (object code) the C compiler generates isn't going to be as expected due to hoisting things out of loops, common subexpression elimination, etc, etc. Still, I think the notion of C being a "low level" language is still a useful label ... if not we need to retire this designation altogether.
- marcosdumay 3y agoAssembly is just a bit lazy, macro-expanded, and the computer's memory address a made up concept. That's indeed an abstraction over the real computer, but it's a lot less things piled up on your virtual computer's model than C. Current assembly is about on the same level as C was when it was created. Current C is so high-level that it doesn't provide any functionality you can't get with a better, more modern language. But yeah, I do agree that "low" and "high" level aren't useful names nowadays.
- ngrilly 3y agoWasn't it the idea of RISC to have a simpler CPU and push the optimization responsibility towards the programmer and the compiler?
- wrsh07 3y agoThis is one of the most interesting programming articles I've read in a while. And it's well written and easy to read! Don't stop at the (inflammatory?) title. * We all agree that c gives you a lot of control to write efficient sequential code * Modern processors aren't merely sequential processors * Optimizing c code for a modern processor is hard because c is over-specified - in order to allow humans to manually optimize their programs (given the c memory model etc), it's hard for compilers to make assumptions about what optimizations they can make It doesn't seem like this is a fundamental problem, though, and c could provide symbols that denote "use a less strict model here" (or even a compiler flag, although I bet incremental is the way to go)
- hooby 3y ago"Low-level" is not a perfectly well-defined technical term, and does mean (slightly) different things to different people. I feel that the article does explain well enough, how the author defines "low-level" for the sake of this article - and the definition being used seems just as fine as any other. And sticking with this specific definition, the conclusions of the article do seem to check out. (But I'm no expert on the subject matter, so I might be wrong about that). I feel that the "value" of the article lies in challenging certain conceptions about C. To me, it doesn't really matter if the article is (completely) right or not - the somewhat indignant response I see happening to the title of the article, and the discussion I see about what "low-level" actually means, seems to prove that some dogmatic beliefs about C are pretty deep-seated. I feel it's always worthwhile to question such dogmatic beliefs.
- GuB-42 3y agoC is low level for at least one reason: manual memory management. Especially with modern hardware, memory management is at the center of programming. For example, Rust prides itself in being memory safe without a garbage collector, memory management is more or less the entire reason for Rust to exist. Why is C fast? Memory. Why is C unsafe? Mostly memory. One of the big reason parallel computing is hard? Concurrent memory access. Functional programming is often surrounded by plenty of mathematical concepts, but a good part of it is to pretend that objects are immutable when behind the scenes, the compiler works with mutable memory. In C, every call to the allocator is explicit, that is, if you are using an allocator at all. Compare to oldschool C++, with new/delete and raw pointers, where you may call the allocator explicitly, but still, a lot happen in destructors, automatically. In modern C++, with smart pointers, it is essentially like a garbage collected language in the sense that allocation and deallocation all happen automatically.
- cmrdporcupine 3y ago"Memory" itself is an abstraction around a much more complicated model (virtual memory / pages) that most programmers remain ignorant of. (Unless you're working on a microcontroller class system, or other system without an MMU but that's a whole other kettle of fish(. Even Rust developers like myself labour within the fantasy that a pointer is, y'know, like an address to memory, a real "physical" thing. Rust (and to some extent C++) introduces some management abstractions in front of this in the form of references and borrowing, but the main concept is still there. In reality the kernel of your operating system has put a giant layer between you and the physical memory, and the "address" and "pointer" are really just handles behind which the OS and MMU do all sorts of shenanigans. "Raw pointers" really aren't raw. They're handles to offsets within pages, which can be all over the place. It would be entirely possible to walk away from the libc & C model entirely and work in a world of pure references interacting directly with VM subsystem pages as some kind of "object handles" and be much closer to the actual operation of the underlying system.
- saagarjha 3y agoWould such a model be generally useful, though?
- HumblyTossed 3y agoI've been programming since the mid 80s, started with the C=64. People have been having the argument that C is low-level vs c is not since at least then. Why do so many smart people waste their friggin' time on such nonsense?
- vivekv 3y agoI am neither a compiler writer or an OS guru. Just an old c programmer. In this article it looks like the entire CPU instruction set was designed to emulate pdp11 to ensure c compatibility. So my naive question. What is stopping a Microprocessor manufacturer to have two instruction sets one that is compatible and one that allows us to fully utilise the modern CPU but with a different programming paradigm? Is that too expensive or hard to do? I genuinely don't know.
- GTP 3y agoI think that there's just isn't any commercial incentive, as we have a ton of legacy C code and the CPU will have to run anyway in the "legacy" mode to run your OS, be it Linux, Windows or MacOS.
- dboreham 3y ago2017
- cmrdporcupine 3y agoYes, C is a set of abstractions like any other language (even assembly.) which attempt to mimic a machine of far less complexity. Unfortunately it's also the wrong set of abstractions for the contemporary era. That said, if you're working in low-level embedded microcontroller world, C's memory model and program structure does in fact look a lot more like those systems.
- AnimalMuppet 3y agoWhat would you say is the right set of abstractions for the contemporary era? Especially for writing things like OSes and device drivers?
- hluska 3y agoIf we ever wonder why more people don’t get into low level programming, this article and the responses are an excellent case study. We’re allowed to make what we know accessible to newcomers and many of us should tone down our arrogance when we have really deep technical conversations.
- deleted 3y ago[deleted]
- mbfg 3y agoPDP-11 is a fast machine?
- ultra_nick 3y agoI just write English these days and have my LLM compile it to Python, so...
- deleted 3y ago[deleted]
- bazoom42 3y agoApparently there is plenty disagreement about what “low level” means. Historically assembler was considered low level, and languages like C with named variables and functions and nested expressions were considered high level. I have also seen C described as mid-level to indicate it is higher level than assembler but “closer to the metal” than say Java. And apparently it is now called low-level by some - wonder what assembler is then? In any case, at this point, low level and high level are only meaningful relative to other languages. The article is questioning how “close to the metal” C actually is, but some of the arguments also applies to assembler, which is not that close to the metal either these days.
- chefandy 3y agoYeah-- my intro C Programming class 20+ years ago began with the professor saying "This class is about C, a high-level programming language used for most UNIX system tools because trying to write all of those things in assembly would make you want to burn your keyboard." It seems like the distinction between C and Assembly these days is less important than the distinction between C and say... Javascript. Which is fine by me- English is descriptive and the people who work in Assembly aren't going to get confused by it.
- ndiddy 3y agoI disagree with the author's point that CPU instruction sets should expose more of the CPU's implementation. This has been tried in the past and failed to work long-term. One example of this is branch delay slots from some RISC processors (such as MIPS and SuperH) designed in the late 80s and early 90s. For those unfamiliar with the concept, it basically means that the instruction after a branch instruction will get run regardless of if the branch was taken or not. This was a short-term benefit, as it meant the job of avoiding pipeline stalls after a branch was left to the programmer, so the processor could be simpler and cheaper than designs without them. However, as time went on, the processor designs evolved with more complex pipelines, so the single instruction wasn't enough to cover the branch delay. Instead, it became a legacy issue that future processors had to deal with for compatibility reasons and made their branch prediction and pipeline logic more complex.
- danielmarkbruce 3y agoI don't think he's saying "expose random implementation details". Exposing the wrong details would obviously be bad. He's just saying c's model has significant shortcomings in the world of modern CPUs.
- rewmie 3y ago> he's saying c's model just doesn't work well anymore. The author argues that C's model does not fit the model he defined himself and claims to be the same model used by everyone. After going through the article, I'm left with the impression that the author's thesis is flawed and relies on a series of strawmen arguments. Among the strawmen we find: * arguing that speculative execution "were added to let C programmers continue to believe they were programming in a low-level language". * claiming that "modern processors are trying to emulate "the same abstract machine as a PDP-11" * "Creating a new thread is a library operation known to be expensive, so processors wishing to keep their execution units busy running C code rely on ILP (instruction-level parallelism)." * etc etc etc. I don't think this opinion piece is grounded on reality, let alone is an objective take.
- 3y ago
- charles_f 3y agoInteresting take, but I think it goes out of its way to prove the definition of low-level to be wrong, while missing that the definition it gives and claim is wrong, in itself is very flexible. What is irrelevant? To a data-scientist, typescript is low-level. You're required to think about structure and compile stuff! To a web developer, C# and Java are low-level because you need to think about the execution platform To an IT developer, C and C++ are low level because you need to think about memory. To a game developer assembly is low level because you need to think about everything. To electronocians everything is high level. To accountants VBA in Excel is low level. To a product manager a word document with any sort of technical words is too low level. If you need to optimize your software to the point where some CPU specific instructions are required, C is too high level because its hiding stuff that is not irrelevant.
- danielmarkbruce 3y agoOn the flip side, maybe CPUs are trying to be too general purpose.
- falcrist 3y agoTo make an article about how C maps to the processor and fail to make any distinction between application programming and embedded programming seems strange to me. After all, C is by far the most common language for programs running on micro-controllers, and it actually does map well to many micro-controller architectures in use today. I'm clearly not the target audience for this article, but I still feel like the author would be well advised to put a little note at the top that says "we're talking about CISC and high-end microprocessors rather than microcontrollers." I'm also not seeing suggestions for languages that do map well to modern microprocessors.
- wzdd 3y agoThis is now five years old, and while obviously the premise is more correct than ever (computers don't look much like a PDP-11 architecturally), the conclusion ("imagining a non-C processor") seems less strong. We are seeing (and were seeing, even in 2018) a strong separation between linear and highly-parallel code, most obviously in the rise of Python for machine learning and scientific computing. It is still very convenient, when performance isn't paramount, to write in a single-threaded style and to a flat memory model. When performance is important, it's then appropriate to switch to a language better suited to parallel programming -- one of the computational-graph languages in something like Pytorch, some other set of primitives on top of CUDA, or even something more experimental like Futhark. Performance-critical code has always had its domain-specific languages, and they seem to be becoming more common, not less, and the hardware is being built to match -- as the CPU+GPU combination common to desktop PCs, as vector extensions to x86 (which have their own primitives making, essentially, a DSL of their own), or things like the M1, which bolt a GPU to a CPU to give both high-speed access to the same system memory. In other words, perhaps what's really out of date is not C, but the concept of a general-purpose language which is equally well-suited to any type of task.
- layer8 3y ago> Consider another core part of the C abstract machine's memory model: flat memory. The C abstract machine only has a flat memory model within a given malloc allocation (and within each local or static object). Relational pointer comparison between different allocations is UB (see e.g. https://stackoverflow.com/a/34973704 https://stackoverflow.com/a/34973704). So C is perfectly fine with a non-flat memory model as long as each object is confined within a flat memory region (by virtue of being allowed to alias it as a char array). You can imagine a C runtime library that provides functions to obtain pointers to different types of memory that don’t share a flat address space. The only restriction is that pointers must carry enough information to compare unequal if they point to different objects. Of course, you might be able to construct a virtual flat memory model from the bit representation of void* or char*, but that’s not quite the same as imposing an actual flat memory model.
- cat_plus_plus 3y agoComputation is only a small part of computing, addressed by languages such as OpenCL and by no means simple, observe constant GameReady driver releases from Nvidia to support each new major game. C is still pretty good at many other parts of low level computing, such as managing state of hardware or allocation of system memory to different tasks. Such tasks are not well suited to parallelism, as they must maintain a globally consistent state. It is perhaps true that CPUs and compilers should execute C code mostly as it is, with only local optimizations to spare programmer of having to decide whether x + x, or x * 2 or x << 1 is faster for example. This would improve system security and reliability while freeing up time to work on great compute languages for vectorizable computations. But, at the end of the day, CPU makers and compiler writers are humans motivated by both career success and less tangible bragging rights. So OF COURSE they will chase benchmarks at the expense of everything else, even when benchmarks have little to do with real life performance in an average case. I have a 13 year old 17 inch MacBook pro I use for some favorite old games. When I fire it up, I don't see any differences in my computing experience vs a 2023 laptop. So whatever advances in CPU/compiler design were made since do not seem to help with tasks I am actually interested in.
- adamrezich 3y agoI've been working on making games for the Playdate (https://play.date https://play.date) over the past few weeks, using their C SDK. it's my first time using C in a decade, since I first learned it in college, and I'm having a surprisingly great time with it. sure, there's tons of weird quirks that take some getting used to—but there's a lot that I've been surprised to find that I missed about it! it's fun to write code that does what you tell it to do, without having to worry about object ownership or any higher-level concerns like that—you just manage the memory yourself, so you know where everything is, and write functions that operate on it straightforwardly. if it's been awhile since you've touched C, I highly recommend giving it a try for a small game project.
- vonwoodson 3y agoThis article begins with victim blaming the software engineers in full-throated support of hardware engineers. If, and I do mean if, anyone should be exalted it is the fact that software engineers have been coping with C as a stable-but-difficult programming language specifically for the benefit of the hardware engineers’ desire to have a stable target. The fact that the specification is ambiguous at all is so that hardware manufacturers can port a reasonably small, expressive, and powerful language to their hardware. And, no, making a new language that targets the platform for the ease of hardware development and exploitation of system-specific benefits is not the answer. In fact, it’s the literal reason why C is still as popular as it is. Nobody wants to learn your programming language, write thousands-to-millions of dollars worth of software, just to have it become obsolete two days after the new-hotness processor comes out. Been there, done that. Alternatively, perhaps, we can place the blame on hardware manufacturers who were looking to cut corners for improved performance and produced insecure machines because they lied to us non-expert hardware users about how fast their systems could go and what we were getting for our money.
- PhilipRoman 3y ago>take a property described by a multidimensional value >project it into a single dimension >split it in the middle, thus inventing two useless artificial categories ("low level", "high level") >get a bunch of highly functioning hackernews 0.1xers to argue endlessly about said useless categories >submit weekly articles "thing X is NOT in my imaginary category Y!!!" >profit Arguing whether or not C is a low level language is about as useful as arguing whether dog-headed men have souls Next up: IO is not a Monad, x86 machine code is not a low level language, RISC-V is not actually RISC, GPL is not actually open source and so on
- ad404b8a372f2b9 3y agoThe taxonomy is not the point of the article. The point of the article is about language and hardware development interactions and whether we are locked in a paradigm in which our reliance on C prevents us from taking advantage of hardware innovations, and in turn create languages which properly makes use of such hardware.
- PhilipRoman 3y agoI see that point, but I still think the article is completely wrong. My disagreement with the article (aside from the flamebait title) is that many of the things the author calls C problems are actually general computing issues. The reason highly threaded processors are not the norm is not that C can't take advantage of them (it does it just as well as 90% of other languages). The problem is that most problems aside from specialized domains are either highly sequential or require too much synchronization. Regarding the immutable memory model example - C does not place any limitations at all. Just declare that modifying such an immutable object is undefined behaviour and let programmers figure it out. Memory already has its complexities with NUMA and such, C programmers have no issue taking advantage of these features. Or maybe take TSX as an example - I'm fairly sure the PDP-11 did not have anything remotely close to Intel TSX and yet it is easy to use in C. Include <magic.h>, write __magicXYZ() and it just works. Sure, existing C programs will run slowly on the author's imagined new processor architecture, but so will programs written in any language except maybe some highly restrictive very high level language (like GLSL on GPUs, etc.). But new programs that are written with such hardware in mind will not in any way be limited by C semantics and if they are (like with mistakes in standard such as errno for math functions...), it will be one compilation switch away from being fixed.
- mpweiher 3y agoI am really surprised that such a bad take has gotten so much airtime, almost as much as that such a gifted developer came up with it. The only way that the title is true is one that is not mentioned in the article: when C became popular, anything that was not assembly was a "high level language". Heck, even some Macro assemblers were considered high level, IIRC. The factors that are mentioned in the article fall roughly into two categories: 1. The machine now works differently. This may be true, but it does so almost entirely invisibly, and the exact same arguments given in the article apply in the same way not just to assembly language, but even to raw machine language. I have a hard time seeing how machine level is not low level. But I guess opinions can differ. What seems inarguable is that machine language is the lowest level available. And if the lowest available level does not qualify as "low" in your taxonomy, then maybe you need to rethink your taxonomy. 2. C compilers do crazy shit now This is also true, but it is true exactly because C is a low level language. As a low-level language, it ties execution semantics to the hardware, resulting in lots of undefined (and implementation defined) behavior that makes a lot of optimisations that some people really, really want to do (but which are far less useful than they claim) really really hard. So C compiler engineers have defined a new language C' which has semantics that are much more amenable to optimisation. Nowadays they try to infer that language C' from the C source code and then optimize that program. And manhandle the C standard, which is intentionally somewhat loose, in order to make the C'' language that looks like C but maps to C' the official C language. Since they were moderately successful, it can now be argued that C has morphed or been turned into a language that is no longer low level. However, the shenanigans that were and continue to be necessary to accomplish this make it pretty obvious that it is not the case that this "is" C. Because, once again, those shenanigans were only necessary because C is a low level language that isn't really suited to these kinds of optimisations. Oh, and of course the rationale document(s) for the original ANSI C standard, which explicitly state that C should be suitable as a "portable assembly language". But then again we already established that assembly is no longer a low level language...so whatever.
- titzer 3y ago> The root cause of the Spectre and Meltdown vulnerabilities was that processor architects were trying to build not just fast processors, but fast processors that expose the same abstract machine as a PDP-11. No, Spectre is the direct result of processors speculatively executing code without respecting the conditions that guard the code. Hands down, processors hallucinate conditions in code. It has nothing to do with the particular computational model, but would happen in any system that speculates conditions. And not just one branch, but a whole series of them. In fact, the processor is usually running with a whole buffer full of instructions that are executing in parallel, having been loaded into the reorder engine using nothing more than (normally highly accurate) statistical predictions.
- titzer 3y ago> On a modern high-end core, the register rename engine is one of the largest consumers of die area and power. Another red herring. Register rename isn't the result of some PDP fetishizing. It is a direct result of using more hardware resources than are exposed in the architectural model. Even if it were a stack machine or a dataflow graph architecture, register renaming is what you do when you have more dynamic names for storage than static names in the ISA.
- intalentive 3y ago“The abstract machine C assumes no longer resembles modern architectures” implies that it might be nice to have a language that maps more directly to what is really happening under the hood. I agree. It would be nice to take the guesswork out of, “How should I write this so that the compiled code has fewer cache misses?” Maybe there is a sweet-spot level of abstraction that allows for more fine-grained control of the modern machine, in the sense that compiled code more or less reflects written code, but not so fine-grained as to be unwieldy or non-portable. Vectorized code that is native to the language could be done with either map functions or Python / NumPy / PyTorch style slicing, which is fairly intuitive. Multithreaded OTOH I’m not sure there is an easy answer.
- openasocket 3y agoI feel like the article advances on two different lines of argument that are difficult to reconcile. The first is that C is not a low-level language, and gives examples like struct padding and signed overflow being undefined behavior. That part makes sense to me, and the argument seems constructive: it seems to propose language features for a hypothetical "real low-level" language. The second argument is that, because of the dominance of C, CPU designers have had to bend over backwards to create something that runs C naturally. Here there are examples like register renaming, flat memory, caching, etc. This argument also makes sense to me, but in the context of the first argument, and the title of the article, I'm not sure how it relates. Taken at face value, this seems to imply that it isn't even possible to create a low-level language on modern hardware, and even machine code is "high-level". This seems to argue that we would have to create a new generation of hardware that exposes much more complexity to the instruction set architecture, and only then could we design a low-level language to take advantage of that. I think both of these arguments have merit, but it's a little disconcerting to put both of them in the same article, and to make the title "C is not a Low-Level Language". I suppose the first argument could go here, and the second argument could have been done in a follow-up article entitled "Machine code is not a Low-Level Language Either".
- Phrodo_00 3y agoIntel's IA-64 supposedly exposed lower levels of the processor to machine code, but I hear it took ages to compile, and compilers never really got to the optimization levels they were expecting (and not being compatible with x86 also didn't help adoption)
- OnlyMortal 3y agoWhen compared to assembler, I’d agree. I grew up with 6502 and 68k. To me, back in the early 90s, C (Mac MPW C to be precise) was an abstract assembler. The code-gen was perfectly readable. Compared to the likes of Python, it most certainly is low-level. These types of language allow developers to rapidly get something going and not just because of the libraries. I’d find it very hard to justify a business position where C has any other role than binding and breaking out into something more abstract. Be that Go or C++, for an example. An argument I used to hear was “performance” from C. I’m not entirely convinced as in a higher language your algorithm may well be better as you can deal with the abstraction. But… people make money coding C.
- assimpleaspossi 3y agoThe paper talks about how C is designed for a PDP architecture and that's the problem. Is there any language that is not that way and can handle parallelism and all the things mentioned in the paper? Yes, I do see Erlang mentioned but I don't think it was considered a solution.
- phendrenad2 3y ago[delayed]