Even on freestanding environments they can and will generate calls to memcpy and memmove. That's insane...
Even on freestanding environments they can and will generate calls to memcpy and memmove. That's insane...
If I write printf("Hello, world\n"), all I care about is that those characters are written to the standard output stream when I run the program -- and that's all the language standard specifies. I rarely even look at the assembly or machine code.
As someone else mentioned, "gcc -fno-builtin" inhibits optimizations like this. Other compilers are likely to have similar options.
> all I care about is that those characters are written to the standard output stream when I run the program
Sometimes people care about a lot more. Such as the ability to hook into a specific function or ensuring the compiler doesn't generate calls to certain functions.
> that's all the language standard specifies
The author clearly cared about the undefined behavior. He had a mental model of what would happen that was perfectly reasonable. It was constantly invalidated by the optimizer.
> I rarely even look at the assembly or machine code.
I do. It's very jarring when you write some code and the compiler deletes some of your calls and reorders the rest. It can really complicate debugging sessions.
> gcc -fno-builtin
Yeah, that's become a standard flag for me. Just checked the documentation and it turns out it's also implied by -ffreestanding which is a better language than hosted C anyway just because it gets rid of all the libc cruft.
I've given up on that. Standards don't compile code so they don't really matter in the end. I'm interested in what my compilers do and the code they generate. I've found I can just tell them to define the formerly undefined behavior, significantly improving the language as a result. No strict aliasing, forcing signed integers to wrap around as you'd expect them to, etc.
But back in DOS days, there was something called Sphinx C--: https://bkhome.org/archive/goosee/cmm/c--doc.htm. A modern cross-arch reincarnation of that could be interesting.
Yeah.
More precisely, what I want is something that:
1. Gives me simple native code ELFs
2. Containing no symbols other than the functions I defined
3. That can interface directly with the Linux kernel with zero dependencies
If I bend C enough it turns into something resembling that. Freestanding C, a couple flags to fix the language and the compiler's inline assembly to fulfill the 3rd requirement. I agree that it's not perfect for the role but C compilers are way too important for me to simply disregard them and look for or invent a new language. I'd be giving up too much.
I wish the newer C standards took the time to define previously undefined behavior instead of adding even more cruft to the standard library. C11 is just the opposite of what I wanted.
Then you have embedded, where the norm is either gcc (which loves to optimize away UB), or bespoke compilers produced by the hardware manufacturer that are usually full of weird bugs in any case.
Given that, is C really that important? I can see the ability to parse C headers as somewhat useful for the sake of interop, just so you could use all the libraries (and not just syscalls); but aside from that?
The way I think of it is that a C program specifies run-time behavior (which consists mostly of I/O). Any generated code is just a way to achieve that. C is not some kind of assembly language. The standard says nothing about CPU instructions or registers.
If you want to use C source code as a way to generate specific assembly or machine code, you'll have to go beyond what the language standard guarantees. If you need to do that, and you have an implementation that helps you with it, that's great.
I wonder if there's enough demand for a language that's independent of the target processor but still guarantees that a source call to a given function actually results in a call to that function, that a "+" operator results in a single addition CPU instruction, and so forth. It's not something I'd have any use for myself, but that doesn't mean it wouldn't be valuable. C isn't that language, but something similar to C might be.
I/O on the other hand is a lot more interesting. They're a very common source of undefined behavior, bugs and interception via custom shared objects. I'd rather the compiler touched them as little as possible. Actually I'd rather not use them at all. I find system calls to be much more ergonomic.
More generally, using system calls directly for things like getting the current time makes sense only if you don't care about portability.
C can support both portable and non-portable code. Both are important.
I get the impression that C is too high-level, and assembly language is too tedious, to fully support your requirements. I sympathize with your situation, but it's not one that most programmers share.
Definitely seems that way to me as well. Still, if I bend C enough it turns into something that's almost what I want. I just wish GCC had a couple more flags to control code generation in these surprising cases.
Of course I care if I want to use the value returned by printf, but if my code uses that value the optimization isn't performed.
I think the semantic gap between assembly language and C is much wider than a lot of people realize. I wonder if a language somewhere in the middle of that gap would be useful to some people. (It probably wouldn't be useful to me.)
Of course. Who else is going to set errno when I pass negative a number?
If you don't like C, you can use a dialect of it by setting some flags: -fno-math-errno if you want to optimize sqrt anyway or -fno-builtin if you don't want the compiler to replace functions calls
> -fno-math-errno
That's interesting, didn't know about it.
Or even simpler: do you demand that `x = sqrt(4)` entails emitting code that calls `sqrt`?