When your computer is a PDP-11, otherwise it is a high level systems language like any other.
When your computer is a PDP-11, otherwise it is a high level systems language like any other.
Your C optimizer is emulating that VM when performing symbolic execution, and the compiler backend is cross-compiling from it. It's an abstract hardware that doesn't have signed overflow, has a hidden extra bit for every byte of memory that says whether it's initialized or not, etc.
Assembly-level languages let you write your own calling conventions, arrange the stack how you want, and don't make padding bytes in structs cursed.
Which language more accurately represents hardware then?
The real answer is obviously Assembly - pick a random instruction from any random modern CPU and I'd wager there's a 95% chance it's something you can't express in C at all. If the goal is to model hardware (it's not), it's doing a terrible job.
Describing C as "high-level" seems like deliberate abuse of the term. The virtual machine abstraction doesn't imply any benefits to the developer.
Isn't this true for most higher level languages as well? C++ for instance builds on top of C and many languages call into and out of C based libraries. Go might be slightly different as it is interacting with slightly less C code (especially if you avoid CGO).
For one, would expect that a low level language wouldn't be so completely worthless at bit twiddling. Another thing, if C is so low level, why can't I define a new calling convention optimized for my use case? Why doesn't C have a rich library for working with SIMD types that has been ubiquitous in processors for 25 years?
The existence of undefined behaviour isn't proof that there is a C "virtual machine" that code is being run on. Undefined behaviour is a relaxation of requirements on the compiler. The C abstract machine doesn't not have signed overflow, rather it allows the compiler to do what it likes when signed overflow is encountered. This is originally a concession to portability, since the common saying is not that C is close to assembly, but rather that it is "portable" assembler. It is kept around because it benefits performance, which is again one of the primary reasons people write C.
C has always been classed as a high level language since its inception. That term's meaning has shifted though. When C was created, it wasn't assembly (middle) or directly writing CPU op codes in binary/hex (low level).
Honestly it doesn't really matter. High level and low level are relative to each-other (and machine language), and nothing changes based on what label you use.
Best thing to do is shrug and say "ok".
What makes C low-level is that it can work directly with the representation of objects in memory. This has nothing to do with CPU features, but with direct interoperability with other components of a system. And this is what C can do better than any other language: solve problems by being a part of a more complex system.
The integral promotion rules come directly from the PDP-11 CPU instruction set.
If I recall correctly so does the float->double promotions.
CPUs started adapting to C semantics around the mid-80's. CPU designers would profile C generated code and change to be able to more efficiently run it.
So which of these languages do you think is a better representation of hardware and not a PDP-11?
No one is claiming it was built for today's processors, just that it puts less obstacles between you and the hardware than almost any other language. Assembler and Forth being the two I'm familiar with.
What's standardized was never as important in C land, at least traditionally, which I guess partly explains why it's trailing so far behind. But the stability of the language is also one of its features.
But sure, if all youre doing is dot products I guess you can write a standard function that will work on most simd platforms, but who cares, use a linalg library instead.
> The semantic descriptions in this International Standard describe the behavior of an abstract machine in which issues of optimization are irrelevant.
This belief that C targets the hardware directly makes C devs frustrated that UB seems like an intentional trap added by compilers that refuse to "just" do what the target CPU does.
The reality is that front-end/back-end split in compilers gave us the machine from the C spec as its own optimization target with its own semantics.
Before C got formalised in this form, it wasn't very portable beyond PDP. C was too opinionated and bloated for 8-bit computers. It wouldn't assume 8-bit bytes (because PDP-11 didn't have them), but it did assume linear memory (even though most 16-bit CPUs didn't have it). All those "checking wetness of water... wet" checks in ./configure used to have a purpose!
Originally C didn't count as an assembly any more than asm.js does today. C was too abstract to let programmers choose addressing modes and use flags back when these mattered (e.g. you could mark a variable as `register`, but not specifically as an A register on 68K). C was too high level for tricks like self-modifying code (pretty standard practice where performance mattered until I-cache and OoO killed it).
C is now a portable assembly more because CPUs that didn't fit C's model have died out (VLIW) or remained non-standard specialized targets (SIMT).
One of the very first systems programming languages was JOVIAL, from 1958. C's inventors were still finalising their studies.
None of them, you use Assembly if you want the better representation of hardware.
Yes, I am quite confident, because I have been dispelling the C myth of the true and only systems programming language since the 1990's.
The other approach, taken by Rust (and to some degree C++), is to nail everything to the floor and force the programmer to express a solution in a specific format that's easier to verify and make guarantees about. Which is fine.
Both approaches have their appeal, which is best depends on context.
C does not have an infinite number of libraries and examples. The number of libraries and examples C has is quite large, and there are an infinite number of theoretically possible libraries and examples, but the number of libraries and examples that exist are finite.
Keep waiting for the examples where they can't do what ISO C allows for, and if the example uses compiler extensions to the ISO C, I also feel within the right to use extensions to those languages on the counter example.