11 ms·
C is low level for at least one reason: manual memory management. Especially with modern hardware, memory management is at the center of programming. For exampl
by GuB-42 3y ago
C 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?
- actionfromafar 3y agoI have thought so for a long time. It could open up execution of functional languages on a truly distributed runtime. Something like the fabled Tao operating system I guess.
- cmrdporcupine 3y agoDefinitely useful in some systems context, especially e.g. database page buffer management.
- jstimpfle 3y agoRaw pointers are how you communicate with your CPU. They are "raw" in the sense that they're just an integer number (not really on the C language level, but they have an integer representation on any actual target like x86) and that you have to synchronize these pointers with the lifetimes of "actual" objects, which are only an abstract concept that your computer doesn't understand. Meanwhile, virtual memory is as close as you can get down to the physical hardware in terms of normal CPU instructions (i.e., not VM management code). VM as a concept is orthogonal to raw pointers, which can be either virtual or physical. Raw pointers are nothing like handles. They need to be manually "synchronized" properly with VM management (which happens completely behind the scenes for 99,99% of userspace code) to make sense but it's not like there is bookkeeping overhead in copying or offseting a pointer, like there would be for a "handle". The point of a handle is that it's use to hold objects, to keep them alive. Raw pointers don't do that.
- anonymous_sorry 3y ago> 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 Is this possible in Ring 3? Or would everyone be running in kernel mode at that point. Even if you do away with that layer, then there may still be a hypervisor lying to the kernel about memory.
- squeaky-clean 3y ago> Unless you're working on a microcontroller class system, or other system without an MMU but that's a whole other kettle of fish So C can do actual memory management, your OS or hardware just won't let you. I've done programming for audio effects gear where memory is directly accessible by real address. Often with different memory chips with different performance characteristics (for cost reasons) corresponding to different pointer value ranges. Just because your machine won't let you do it doesn't mean C isn't capable of it.
- marcosdumay 3y ago> Why is C unsafe? Mostly memory. C can't even do all of integral arithmetic safely. It's a language that goes really out of its way to add unsafety.
- jstimpfle 3y ago[flagged]
- JonChesterfield 3y agoPost might have signed integer overflow => undefined behaviour => delete all the code in mind. Which can be avoided if you remember that hazard is there. What's integer divide by zero in C? Would you consider that safe?
- jstimpfle 3y ago[flagged]
- JonChesterfield 3y agoDecent chance integer divide by zero will kill your process. Might even call it a floating point exception. Maybe that qualifies as safe to you, seems not-safe to me. Compelling alternative would be for it to return zero. Which is safe. But C doesn't do that. So you have to remember to never write '/', and instead call my_divide, which has a branch in it.
- jstimpfle 3y ago> Compelling alternative would be for it to return zero. Which is safe. Is it? Or is it just another opportunity for a bug to hide?
- jovial_cavalier 3y ago>Compelling alternative would be for it to return zero. That's actually way less safe than crashing. If your code doesn't handle the case where the denominator is zero, it is likely that the logic around your division doesn't consider it either. The behavior you suggest would take a rapidly increasing number and instantaneously set it to 0, then silently pass it into the logic that was humming along up to that point. Since there's no symbol for NaN in integers, there is no safe way to represent `x / 0`, and thus the best way to handle it is to fault. Even better would be if the compiler caught it and warned you.
- pjmlp 3y agoBASIC also can do manual memory management, not only that, it had a whole computer generation for itself, in computers not able to have a full ISO C implementation.
- theamk 3y agoSo does Python (via ctypes) and pretty much every language we consider "high level". But in BASIC your default approach is "DIM names$(count)" which "magically" manages your memory for you.. which is why we consider it higher level than C.
- pjmlp 3y agoThat is hardly different from malloc(count * size), REDIM exists (aka realloc()) and many BASICs do offer the free variant as well. In fact, there is hardly any difference between VMS BASIC and VMS C in terms of what is possible, if we want to take the discussion outside of 8 bit versions.
- 2-718-281-828 3y agovery interesting observation. never thought about how memory is central to all those concepts and technologies.
- systemBuilder 3y agoIn my lifetime the memory hierarchy has grown from 3 levels (register, main memory, disk) to at least 8 levels (register, L1 Cache, L2 Cache, L3 Cache, VM, main memory, disk, web). A lot of people like the guy who wrote this paper have no patience for a language that can still run most 1971 C programs! Every year people who do not understand why C is so successful try make a name for themselves by breaking what makes the language great (such as the complete agnostic set of control structures). C has successfully remained portable and performant for 50Y+ because of its flat memory model (with a few tweaks such as "volatile".)
- grotorea 3y ago> C is low level for at least one reason: manual memory management. Especially with modern hardware, memory management is at the center of programming. Ok, but even with C we can't actually low level manage memory how the processor does it. You can't tell the processor what to keep or not on what level of cache, what to send to virtual memory, etc. It's lower level than say Python but I don't think it is low level memory management in the way PDP-11 C was.
- Chabsff 3y agoA lot of that is just a property of modern OSs, with good reason, intentionally not exposing these features to userspace processes. It's not really a function of the language itself.
- grotorea 3y agoHmm, true for virtual memory, didn't think of that, but CPU caches are inside the processor, can even the kernel control it at all?
- Chabsff 3y agoIt's not really a question of whether or not it's feasible, but rather: If the hardware were supports such operations, would they be expressible within the language? If they are, then we should point at the accessibility of the functionality rather than C. In this case, I could definitely see `set_cache_behavior(ptr, len, options)` being perfectly reasonable, so I'd argue that, again, the fact that we can't do it is more a property of the environment than the language itself.
- wang_li 3y agoYou can control what goes into cache if you want to. The effort to make an open source bios do this in order to have working memory before the DRAM controllers are initialized.[0] 0. https://www.coreboot.org/data/yhlu/cache_as_ram_lb_09142006.pdf https://www.coreboot.org/data/yhlu/cache_as_ram_lb_09142006....
- 3y ago
- crabbone 3y agoC is neither fast nor low-level... none of these descriptors have any meaning. It's a pointless discussion when you don't care to explain how you use the words that obviously have many related but different ways to interpret them.
- MrBuddyCasino 3y ago> C is low level for at least one reason: manual memory management. Manual memory management isn't that much faster than a modern GC, sometimes even slower. I'd argue that C programs are typically fast because there is just less rope to hang yourself with, leaving aside memory safety. The anemic abstractions provided in the language and the tiny stdlib means it takes a lot of work to achieve something, so developers simply do less. There isn't even a Hashmap (or a proper String), while in Kotlin, you can perform a deep copy of the object graph and convert it to json in parallel in a single line if you so wish.
- johnnyjeans 3y agoThis is a common misconception that stems from a misunderstanding of why manual memory management is "fast". It has nothing to do with the actual process by which you request and release memory. Manual memory management being faster than GC is a function of controlling memory layout, which is one of the (very) few low hanging optimization fruits that mortals like you or I can do on contemporary CPUs. It also has to deal with the fact that dynamic allocation is slow, and being able to get it out of the way with a single allocation in the first moments of a process's life is an immensely important tool in the optimizer's toolbelt. > The anemic abstractions provided in the language and the tiny stdlib means it takes a lot of work to achieve something Which has the additional effect of forcing you to be a bit smarter about how you do things, to be less wasteful. It forces you to contend with everything you want to do, to consider it and the cost associated with it. Built-in, general-case abstractions are nice when under time constraint and hacking something together, but it doesn't make for good software. Not only is it almost guaranteed to be slower than a properly constructed purpose-built solution, but it also removes your view from thinking about the cost of every single thing you're doing. It makes it easier and attractive to overuse abstractions, to over-engineer solutions, and to approach problems from a standpoint where you simply throw the kitchen sink at the problem because that's the only thing you can think of.
- jstimpfle 3y agoLove your words. 1000 upvotes if I could. For balance, the faster machines get, the more problems are most effectively solved by throwing the kitchen sink at them.
- kuchenbecker 3y agoCache invalidation is hard :) almost as hard as semantically naming things in a way that is clear now, and in the future.
- staunton 3y ago... the famous Two Hard Things, together with off-by-one errors.
- tmtvl 3y agoconcurrency With 3 it's Things Hard.
- kuchenbecker 3y agoConcurrency is hard because it's difficult to know if the value you have cached in memory is valid or invalid. As soon as a value is read it's cached. Therefore Concurrency reduces to Cache invalidation and there are again only two hard problems :)
- pornel 3y agoC's memory management is its own abstraction. malloc and free are library functions. They're an abstraction not just over hardware (that doesn't have anything bytewise allocated like that), they even abstract away the way operating systems allocate memory. You don't get direct access to the stack in C either. Stack frames are abstracted away, and you only get longjmp. If you pay attention to Undefined Behavior and strict aliasing, you don't even get that much access to poking around memory.
- deleted 3y ago[deleted]