4 ms·
Got an alternative? The trouble here is that lots of programmers want/need a language that is like a portable assembler. It's all well and fine to say that C i
by tropo 8y ago
Got an alternative?
The trouble here is that lots of programmers want/need a language that is like a portable assembler. It's all well and fine to say that C isn't such a language, but then what language shall we use?
Since there isn't an alternative, C needs to be that language.
- saagarjha 8y agoThe trouble is that it's really not possible to be a portable assembler. Sure, most processors have instructions to do arithmetic operations, but at some point you're doing things that not all processors support. So either you're going to have to drop the "portable" or "assembler" part at some point.
- jcranmer 8y agoThe alternative is to sit down and design a new language for this task, rather than to try to force C to be that language, which is a task that it is successful enough sufficiently often to be touted as the solution and fails at sufficiently often for people to complain that it fails. The problem is that almost no one needs a portable assembler, and even those who want it very often only want it for very small snippets of code. If you want to specify something very exactly that is legal only because of the particulars of your machine, then use assembly. There are certainly a few things that compilers could expose that are poorly exposed in most languages (floating point environment, various flag bit manipulation including checked overflow and add-with-carry, vector instructions, synchronization barriers, underaligned types). But you don't need portable assembly for those things, just better compilers.
- userbinator 8y agorather than to try to force C to be that language Ironic, considering that C was specifically designed for that from the beginning.
- pjmlp 8y agoOn the context of PDP-11 class hardware. C isn't the first portable systems programming language, it just became ubiquitous thanks to UNIX.
- monochromatic 8y agoInline assembly.
- lambda 8y agoIt may be impossible for you to get what you're looking for out of a "portable assembler." There's a fundamental tension between low-level control, optimization, and portability. Either you will have code that will run dog-slow on certain architectures, as the compiler has to jump through hoops to do things like unaligned reads and writes that you ask it to do, or it will simply fail to compile certain architectures, or you will encounter undefined behavior on other architectures. Whichever way it happens, you'd have to rewrite the code for different architectures. If you're rewriting the code on different systems, why aren't you just using an assembler instead of a portable assembler? However, Rust may be the answer you're looking for. Right now, in safe code, all operations are well defined; there are a few compiler and standard library bugs which allow undefined behavior, but that's not intentional and many of them are just waiting on some compiler refactoring to land before they will be fixed. In safe code in Rust, you can pass around references (bare pointers with no GC), work with byte arrays, allocate things on the stack or on the heap, etc. It does provide some restrictions in what you can do to make it possible to check statically that all of your code is safe. There is also an unsafe subset; this allows the "I know what I'm doing" kind of manipulations you can do in C. Of course, this means that you could encounter undefined behavior; if you do something incorrectly, it could cause arbitrary behavior. The compiler has to be able to rely on certain guarantees that you uphold, because otherwise it could perform not even the most basic of optimizations, nor in many cases even know how to correctly compile the code. There is an active effort to specify the exact guarantees applied to unsafe code. Right now it's "whatever the current compiler and LLVM happen to do", which is not particularly good in terms of guarantees. The idea is to try to make something which will be amenable to validating the code. If undefined behavior is sufficiently well specified, then you can compile the code in an instrumented mode which will detect undefined behavior (at significant cost in speed), and you can run your test suite in that mode to get reasonably good guarantees that you don't have UB in your program (as long as your test suite has sufficient coverage). Clang and GCC have similar modes, undefined behavior sanitizers, that compile code with instrumentation that can detect certain classes of UB. Since C's memory model wasn't designed with this in mind, and C has a fairly weak type system, they can't cover everything. The hope with the current work on Rust is that a memory model can be formally defined such that all UB could be detected by such a sanitizer, so as long as you have sufficient test coverage, you can be confident of avoiding UB. Of course, there are other tools you can use as well to avoid UB, like formal proofs of correctness, but it can be quite difficult to fully specify the language in a formal system, and writing proofs of correctness in such systems is not a trivial task.
- EpicEng 8y ago>Since there isn't an alternative, C needs to be that language. I'd be interested in your ideas as to how this would be possible.
- pjmlp 8y agoNo, inline Assembly or intrinsics with conditional compilation are the alternatives.
- burfog 8y agoThose are almost never portable at all. What we're looking for here is a certain sort of sane non-extremist portability. We want to support hardware that actually ships today as the primary application processor for a cell phone, laptop/notebook, tablet, desktop, or serious SQL or web server. We don't want to think about opcodes or registers. The hardware in use today is ARM, x86, and POWER. All of these normally support unaligned accesses, and so we expect that of the language. We see it in x86 except with that weird instruction that gcc wrongly used (it is slower) in this article. We see it in ARMv7 and ARMv8. We see it in POWER, going back at least to the PowerPC days. All 3 of those architectures support little-endian code. It is now extremely rare to find big-endian ARM, and POWER is rapidly heading that way. It is essentially required by the web now, due to javascript Typed Arrays exposing endianness. RISC-V is the same way: little-endian with support for unaligned access. This is the future. The type of control and predictability desired is as follows: If I have a 32-bit value, and I shift it left by 40 bits, there are only two reasonable answers. The compiler can do exactly as requested, producing a zero. The compiler can mask the shift down to 5 bits first, because hardware commonly does this, causing the shift to be by 8 bits. It is not at all OK for the compiler to do weird shit however, such as assuming that the code path can never be hit and then doing dead code elimination. If I have a 32-bit 0x7fffffff and I shift it left by 1, that should produce the obvious result even if the data type is signed. (it is -2 in that case) Again, it is unacceptable to do weird shit. It is simply not OK to presume that my code will not run (due to an allowance made in the C standard for some 1960s hardware) and then do dead code elimination. If I have a signed value and I shift it right, I expect the compiler to use the opcode for a signed shift. At least gcc promises to do this.
- pjmlp 8y agoYou can already do that in any mostly safe systems programming language (Ada, Modula-2, D, System C#, Rust, Modula-3, Object Pascal, ....). For the 1% non portable stuff there is unsafe code blocks or Assembly.