C for All
plg.uwaterloo.ca
plg.uwaterloo.ca
* `if` / `case` / `choose` improvements look fine, though not that important.
* Exception handling semantics aren't defined.
* `with` is pointless and adds gratuitous complexity to the language.
* `fallthrough` / `fallthru` / `break` / `continue` are all just aliases for `goto`. It's not obvious to me that we really need them.
* Returnable tuples look very nice.
* Alternative declaration syntax looks like a nightmare. If we were redesigning C from the ground up, a different declaration syntax might be better, but mixing two syntaxes is a terrible, terrible idea.
* References. Why? They only add confusion.
* Can't make head or tail of what `zero_t` and `one_t` are about, or why they would be useful.
* Units (call with backquote): gratuitous syntax, unnecessary and confusing.
* Exponentiation operator: gratuitous and unnecessary.
I suspect it's the same problem C++ has/had (C++11 fixed it) with bools (see the safe bool idiom [0]). Basically treating a type like an integer (arithmetic object) and boolean (logical object) at the same time is problematic (especially for a "system" type meant for extending implicit system behavior). Because then I can do `if(BoolObject < 70)` when I only meant for `if(BoolObject)` to work (where "BoolObject" is some object evaluating to a bool, and by evaluating I mean coercing/casting).
Here it looks like they approached it by making 0/1 (effectively C's false/true) different types and relying on their simpler/more-powerful type system (e.g. because they don't have to worry about C++'s insane object system). Not a terrible idea if they were otherwise actually sticking to their goal of "evolving" C (most of their features are radical departures from the language like exceptions). C++11 solved it by clarifying how implicit explicit casting [sic] of rvalues works in certain keywords (which I strongly doubt anyone can say was the simpler way of solving the problem).
[0] https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Safe_bool
An interesting exercise might be to figure out how to do the Golang feature set, or some useful subset of it, in a C-compatible or mostly-C-compatible syntax.
I do like the returnable tuples, though, and the parametric polymorphism is pretty nice.
WHAT?
discrim = b² - 4ac; // Standard notation
float discrim = pow(b, 2) - 4*a*c; // C
float discrim = b \ 2 - 4*a*c; // C∀
I would argue that these are presented here in descending order of readability.Also its typing rules are really complicated; apply it to two integers and magically you are thrown into the floating-point world where you can never be completely certain of anything, but if you use an unsigned exponent then you stay safely in integer-land.
I assume there's a double asterisk there, and it's being eaten by the formatter?
int a = 1;
int *b = &a;
int c = a ** b;
// Am I casting b to an int (returning it's address) and exponenting it or am I derefrencing b and multiplying it's result with a?Hmm I'm pretty sure practically every programming languages have it. It usually looks like "!=" or "<>".
The even more obscure logical XNOR is usually denoted "==" or "="
'||' and '&&' are distinct tokens in C as far as i know, i.e. not handled as two consecutive '|'s or '&'s.
So your example would unambiguously be parsed as "a to the b:th power". Whereas the other case would need explicit parens:
int c = a * (*b);
Similar example for &: int a = 1;
int b = 1 && a; /* 1 LOGICAL_AND a */
int c = 1 & (&a); /* 1 AND address of a */ float discrim = b*b - 4*a*c;
Using pow for a small integer power is a no-no: less efficient and less accurate.I agree that \ is an awkward choice. A Fortran-like double asterisk ∗∗ is out because of ambiguity with pointers; single caret ^ out because it is already reserved for bitwise xor. Maybe double caret ^^ or asterisk-caret ∗^ could be used? That would read okay :
double discrim = b^^2 - 4*a*c;
double discrim = b*^2 - 4*a*c;Using pow for a small integer power compiles into the exact same code: https://godbolt.org/g/CjoHdJ
Using pow for a small integer power is a no-no: less efficient and less accurate.
I think you missed the point.I don't have the feeling that the authors appreciate the appeal of C as a simple language that maps closely to hardware features.
This is a big random collections of extensions that piqued some implementor's fancy. There is seemingly no effort at narrowing down to the cleanest or most important ideas. It totally kills the clean, simple aesthetics of the the base C languge.
Alumni
Ph.D.
Glen Ditchfield, 1992
Thesis title: Contextual Polymorphism
Masters
Thierry Delisle, 2018.
Thesis title: Concurrency in C∀.
Rob Schluntz, 2017.
Thesis title: Resource Management and Tuples in C∀.
Rodolfo Gabriel Esteves, 2004.
Thesis title: Cforall, a Study in Evolutionary Design in Programming Languages.
Richard Bilson, 2003
Thesis title: Implementing Overloading and Polymorphism in Cforall
David W. Till, 1989
Thesis title: Tuples In Imperative Programming Languages.
USRA
Andrew Beach, Spring 2017.
Line numbering, Exception handling, Virtuals
So basically, it's a research language, more-or-less developed one student at a time.1) has some downright idiotic things (exceptions, operator overloading)
2) has a few reasonable, but mostly inconsequential things (declaration inside if, case ranges)
3) is missing a few real improvements (closures, although it is not clear whether the "nested routines" can be returned)
Could you please provide a code snippet of this kind? Hard for me to visualize otherwise. Thanks.
typedef int (*my_func_ptr_t)(const void*,const void*);
typedef union { my_func_ptr_t func_ptr; int* data; } my_union_t;
void my_func (my_union_t param);
// Now exploiding it
void my_func (union { int (*func_ptr)(const void*,const void*); int* data; } param);
Of course this is very basic example, but there is hope to make it into an IOCC entry.This nonsense again. I don't get this "undefined behavior" cliche. It seems it became fashionable for some people to parrot it like a mantra as a form of signaling. Undefined behavior just refers to something that is not covered by the international standard, and therefore doesn't exist nor should be used, but an implementation may offer implementation-specific behavior.
I had the impression that some things are UB because they can't specify a single behavior that would be efficient across all platforms C targets.
According to the spec, it literally can. The compiler is free to replace your entire program with unrelated functionality. It can ever do a different thing each time it compiles the program.
There are implementation specific behaviors (# of bits in a char), which are different.
Implementation-defined behavior limits portability in the way you describe, but UB's not the same thing. IB should behave the same way in the same implementation. UB doesn't have to.
Undefined is out of the scope of the language entirely. Using a non-existent index into an array, for example. While you might reasonably expect the program will just look past the end, there is not guarantee it will do so. Optimizing compilers, in particular will assume such a thing cannot happen, and can assume a code branch that does something like this is impossible to reach and discard it entirely.
No one will "fix" such an optimization bug, because the code behind it is valid for ASTs that may have been put into that form from conforming code generated by macros and branches that wouldn't be called. There's nothing to fix.
You're telling it to do something impossible, and it's assuming it can't happen.
An example is what the C standard calls "trap representations": Bit patterns which fit into the space occupied by a specific type, but which will cause a hardware trap (exception, interrupt, what have you) if you actually store them in a variable of that type. The only type which cannot have trap representations is unsigned char. Basically, what it amounts to is this: C compilers don't compile to a runtime, they compile to raw machine code with, perhaps, a standard library. If you do something the hardware doesn't like when your program runs, well, the C compiler is long gone by that point and the C standard makes no guarantees.
More prosaically, storing to a location beyond the end of an array might not cause a segfault. It might corrupt some other array, it might cause a hardware crash, it might even corrupt the program's machine code. Because C is explicitly a language for embedded hardware, with no MMUs, no W^X protection, and no OSes, the C standard can say very little about such things.
Actually I didn't. My point was rather obvious: the whole point of the standards specifying UB is precisely to let implementations define the behavior themselves.
That's literally the definition of implementation-defined behavior.
Undefined behavior really means undefined; in terms of the C language, there are no constraints on behavior. Sure, you might get a result one way on one implementation, but if you rely on that you're technically writing a dialect of C, and need to let the compiler know using flags.
Legalistically, I guess it could (the behavior isn't defined, after all), but typically the optimizer makes some valid-only-if-the-code-is deductions and things snowball from there....
This used to be the case. Signed integer overflow for instance is undefined because some CPUs go bananas when you try that. Other platform performed 2's complement just fine, and we used to be able to rely on this.
No longer.
See, the standard doesn't say "implementation defined". It doesn't say "undefined on platforms that go bananas, implementation defined otherwise". It says "undefined" period.
Signed integer overflow is undefined on all platforms, even your modern x86-64 CPU. Compiler writers interpreted it as a licence to assume it never happens, to help optimisations. For instance:
int x = whatever;
x += small_increment;
if (x < 0) { // check for overflow
abort(); // security shut down
}
proceed(x); // overflow didn't happen, we're safe!
Here's what the compiler thinks: int x = whatever;
x += small_increment;
if (x < 0) { // only true if signed overflow -> false
abort(); // dead code
}
proceed(x);
Then the compiler simply deletes your security check: int x = whatever;
x += small_increment;
proceed(x);
Don't listen to Chandler Carruth, nasal demons are real. Some undefined behaviours can encrypt your whole hard drive, assuming they're exploitable by malicious inputs.Now, if you used
unsigned int x = whatever;
...
if(x < 0)
There would be an obvious case for removing that if. $ cat undefined.c
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
int x = INT_MAX;
if (x+1 > x) {
printf("%d > %d\n", x+1, x);
} else {
printf("overflow!\n");
}
}
$ gcc --version
gcc (Ubuntu 7.2.0-8ubuntu3.2) 7.2.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ gcc undefined.c && ./a.out
overflow!
$ gcc -O3 undefined.c && ./a.out
-2147483648 > 2147483647 $ cat undefined.c
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
int x = INT_MAX;
if (x+1 < 0) {
printf("%d < 0\n", x+1);
} else {
printf("overflow!\n");
}
}
$ gcc --version
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ gcc undefined.c && ./a.out
-2147483648 < 0
$ gcc -O3 undefined.c && ./a.out
overflow!
The security check is gone all the same.There are cases where the compiler removes misguided overflow checks, since "perform UB, then check whether it happened" doesn't actually work, but your example is not such a case.
This raises an interesting point, because in many cases, assuming 2's complement and wrapping around, checking for overflow after the fact, as opposed to preventing it from happening in the first place, is actually easier. (And actually works if you use the `-fwrap` flag.)
The right thing should be easier to do than the wrong thing. It's a shame this is not the case here.
This is entirely different than implementation defined in that a conforming compiler has to document the behavior they implement and do it consistently.
For example, there are quite a few people who would like to see a C where you can't actually write this:
// int x, y;
if(++x < y) { ... }
...because, well, the behavior of integer overflow is undefined in C, so that code could technically do anything, even though it seems perfectly innocent, especially when coming from a checked language.Of course, you can't do anything in the C standard to require that this code work as-is, because the C standard applies to architectures where mutually-exclusive things happen under integer overflow. But you can always just disallow it completely, and require that people use intrinsics that are explicit about what overflow behavior they expect (where that behavior reduces to plain output on target architectures that follow it, and to a shim on target architectures that don't. You know, like floating-point support, or atomics.)
I don't believe that's the case for plenty of reasons, such as:
- compilers already do that ( yeah, it's one of those RTFM things. See for example GCC's undefined behavior sanitizer)
- the standard already specifies exactly what it is left undefined, thus it's a compiler-related issue (see point above)
Let's face it: some people mindlessly parrot the"undefined behaviour" mantra just for show.
I believe 99% of what people care about the C standard doing, re: UB handling, is requiring compilers to make certain behaviours the default, rather than hidden behind different flags that C newbies who don't understand UB (who thus code most of the bad C!) won't ever set.
well, no they don't. UBSan is at run-time because most UB is impossible to catch at compile-time.
No compiler is going to go out of its way to compile an increment into the machine's usual increment instruction and an additional overflow check that does whatever, just because it can. It's going to compile it into the machine's usual increment instruction and what happens on overflow is what happens naturally.
It's as absurd as claiming that even "x + y" can invoke undefined behaviour, because while the standard allows it, any compiler that compiles such an addition to anything other than the machine's addition instruction (i.e. with implementation-defined effects), to speak nothing of adding the additional(!) checks to deliberately do something else, is clearly not benefiting anyone.
"In theory, there is no difference between theory and practice. In practice, there is." Pure fearmongering, IMHO.
In order to get maximum performance, the compiler is allowed to assume that the programmer doesn't invoke undefined behavior. In other words, it can replace code with something that is equivalent in the presence of UB, but does something totally different in the absence of UB. See e.g. https://blog.regehr.org/archives/767 for some examples of how this can go wrong. (My favorite is the third one.)
What people are actually worried about is when the compiler starts removing - not adding - seemingly unrelated code in a hard to reason about fashion. And compilers absolutely will go out of their way to do this in the name of optimization and performance. And it will do this because it got smart enough to prove that the "unrelated" code can't run without first technically invoking undefined behavior, at which point it can jump to the wild conclusion that it must never actually execute (or that it can remove the code even if it does, because it's legal for the compiler to do anything after invoking undefined behavior - including not execute that code!)
Sometimes the removed code is important security checks, leading to CVEs, hotpatches, etc. - this is not theoretical, and is not remotely new at this point: https://www.grsecurity.net/~spender/exploits/cheddar_bay/exp...
It also makes reporting compiler bugs annoying, as you first have to definitively prove to yourself and the compiler guys that you've actually got a compiler bug, rather than a compiler "feature" of aggressive optimization within the letter of the C++ standard. It's only out of pure stubbornness that https://gcc.gnu.org/bugzilla/show_bug.cgi?id=84658 got reported upstream, I was assuming it was UB in our codebase most of the way down and thus INVALID as a compiler bug...
But perhaps CVEs and expected behavior being borderline indistinguishable from compiler bugs to most C and C++ programmers I know is just "fear mongering" as you say. IMNSHO, it's not \o/
See https://blogs.msdn.microsoft.com/oldnewthing/20140627-00/?p=... for a very detailed example.
See https://blog.regehr.org/archives/1307 for some strict aliasing examples.
The problem is that you can do everything in portable C that you can do with undefined behavior in C, and often in a more straightforward fashion.[1] The compiler won't tell you that you are doing it wrong; there are many examples, tutorials, and books that encourage you to do the wrong thing. The undefined behavior will work until you switch to a different system. Why allow the wrong thing to continue to happen?
[1] A very good example of this is endianness:
https://news.ycombinator.com/item?id=16189110
https://commandcenter.blogspot.se/2012/04/byte-order-fallacy...
Undefined behavior just refers to something that is not covered by the international standard, and therefore doesn't exist nor should be used, but an implementation may offer implementation-specific behavior
Indeed. Even the standard itself, to quote its definition of undefined behaviour (emphasis mine):
"behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this International Standard imposes no requirements NOTE Possible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message)."
The fact that the standard "imposes no requirements" should not be taken as carte blanche to completely ignore the intent of the programmers and do entirely unreasonable things "just because you can", yet unfortunately quite a few in the compiler/programming language community think that way.
It's why I'm very encouraging of more programmers writing their own compilers and exploring new techniques, to move away from that suffocating and divisive culture. Compilers should be helping users, not acting aggressively against them just because it's allowed by one standards committee.
Other language communities that put correctness before performance at any cost, don't share this mentality, including the compiler writers.
Exceptions make manual memory management easier because a proper exception system has unwind-protect[1]. Exceptions are just movements up the stack - exceptions combine naturally with dynamic scoping for memory allocation (memory regions/pools). This kind of memory management was used in some Lisp systems in the 1980s, and made its way into C++ in the form of RAII. By extending the compiler you can add further memory management conveniences like smart pointers to this scheme.
Now if you want to talk about something that actually makes manual memory management a total nightmare, look at the OP's suggestion for adding closures to C.
[1] http://www.lispworks.com/documentation/HyperSpec/Body/s_unwi...
So basically you're saying, before adding exceptions, add RAII-like memory management, and then actually add exceptions. I like both features, but am not sure how you'd wedge RAII into C. Any ideas on that?
The exception implementation isn't done yet, but it's waiting on (limited) run-time type information, it already respects RAII.
A new GCC or LLVM frontend, or is it a transpiles-to-C implementation ala. Nim or Vala?
I was curious about the implementation because I've had rough experiences with Vala and Nim's approach. Unlike with "transpiles-to-js" languages, transpiling to C has some tooling gaps (debugging being the big one). I admittedly don't have a ton of experience with either language but I couldn't find a plugin that gave me a step-through debugger for something like CLion or VS Code. You can debug the C output directly but this will turn off newcomers and assumes the C output is clean.
We intend to write a "proper compiler" at some point (probably either a Clang fork or a Cforall front-end on LLVM), but it hasn't been a priority for our limited engineering staff yet. I think we are getting a summer student to work on our debugging story (at least in GDB -- setting it up so it knows how to talk to our threading runtime and demangle our names), and improving our debugging capabilities has been a major focus of our pre-beta-release push.
forall(dtype T | sized(T))
T* malloc() { // in our stdlib
return (T*)malloc(sizeof(T)); // calls libc malloc
}
int* i = malloc(); // infers T from return typeMy idea was that if it is better to do as much compile time checks as possible before you introduce run-time checks. Does that void pointer protection run faster that code that was checked at compile time? How?
void* malloc_T(size_t sizeof_T, size_t alignof_T) {
return malloc(sizeof_T);
}
int* i = (int*)malloc_T(sizeof(int), alignof(int));
In this case, since the compiler verifies that int is actually a type with known size (fulfilling `sized(T)`), it can generate all the casts and size parameters above, knowing they're correct.[1] To anyone inclined to bash my definition of polymorphism, I'm mostly talking about parametric polymorphism here, though Cforall also supports ad-hoc polymorphism (name-overloading). The phrasing I used accounts for both, and I simplified it for pedagogical reasons.
C does not have memory management in any way period. The C standard library does. How you get to something with dynamic scoping like RAII in C is to use a different library for managing memory. For example Thinlisp[1] and Ravenbrook's Memory Pool System[2] both provide dynamically-scoped region/pool allocation schemes.
Yes it does, as language extension on gcc and clang.
It is called cleanup attribute.
https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attribute...
https://clang.llvm.org/docs/LanguageExtensions.html#non-stan...
the suggested syntax is ridiculous. What is this punctuation soup?
void ?{}( S & s, int asize ) with( s ) { // constructor operator
void ^?{}( S & s ) with( s ) { // destructor operator
^x{}; ^y{}; // explicit calls to de-initializeAnd it can easily get very trick in a language like C where you don't have garbage collection and you have manually memory management, it's easy to capture things in a closure and then deallocate them, imagine if a closure captures a struct or an array that is allocated on the stack of a function for example.
I think we don't need closures in C, the only thing that I think we would need is a form of syntactic for anonymous function, that cannot capture anything of course, it will do most of the things that people uses closure for and doesn't have any performance problems or add complexity to the runtime.
Not always! Rust and C++ closures don't need to allocate in every case. I can speak more definitively about Rust's, but as long as you aren't trying to move them around in certain ways, there's no allocation, even if you close over something.
Consider this sum function, which also adds in an extra factor on each summation:
pub fn sum(nums: &[i32]) -> i32 {
let factor = 5;
nums.iter().fold(0, |a, b| a + b + factor)
}
The closure here closes over factor. There's zero allocations being done here.If you want to return a closure, you may need to allocate. Rust will let you know, and the cost will be explicit (with Box). That's where my sibling's comment comes into play.
Ah, I wish Blocks[0] would have made to into the C language as a standard†... Although you can use them with clang already:
$ clang -fblocks blocks-test.c # Mac OS X
$ clang -fblocks blocks-test.c -lBlocksRuntime # Linux
Since closures are poor man's object, I had some fun with them to fake object-orientedness[1].† or at least that the copyright dispute between Apple and the FSF for integration into GCC would have been resolved (copyright transferred to the FSF being required in spite of a compatible license).
[0]: https://en.wikipedia.org/wiki/Blocks_%28C_language_extension...
[1]: https://github.com/lloeki/cblocks-clobj/blob/master/main.c#L...
So if one finally manages to get a safer C variant that finally wins the hearts of UNIX kernels and embedded devs, it is a win for all, even those that don't care about C on their daily work.
Until it happens, that lower layer all IoT devices and cloud machines will be kept in C, and not all of them will be getting security updates.
I'm going to strongly disagree with that statement.
As far as I'm aware, one of the very few toolchains that even try to improve on this over C are Ada/SPARK.
Global mutable state is marked as Unsafe in Rust.
> nor will it make subsystem supervision work correctly
Erlang is built specifically around this concept.
Perfect is the enemy of good here, throwing out a whole language due to one case doesn't help anyone.
It's also the simplest way to avoid dynamic allocation and the associated OOM issues. So, short of doing static analysis to bound heap usage at compile time, that makes things worse.
And Toyota already got the static analysis wrong for their stack usage. At least globals will fail to compile if they won't fit.
[0] https://www.transportation.gov/briefing-room/us-department-t...
That wasn't the final word, though. I believe this is what the GP was referring to:
> When NASA software engineers evaluated parts of Toyota’s source code during their NHTSA contracted review in 2010, they checked 35 of the MISRA-C rules against the parts of the Toyota source to which they had access and found 7,134 violations. Barr checked the source code against MISRA’s 2004 edition and found 81,514 violations.
...
> Their descriptions of the incredible complexity of Toyota’s software also explain why NHTSA has reacted the way it has and why NASA never found a flaw it could connect to a Toyota’s engine going to a wide open throttle, ignoring the driver’s commands to stop and not set a diagnostic trouble code. For one, Barr testified, the NASA engineers were time limited, and did not have access to all of the source code. They relied on Toyota’s representations – and in some cases, Toyota misled NASA.
http://www.safetyresearch.net/blog/articles/toyota-unintende...
Oh dear no. Certainly not.
Read https://users.ece.cmu.edu/~koopman/pubs/koopman14_toyota_ua_...
My favourite part from is, "Watchdog kicked by a hardware timer service routine".
A watchdog timer is a piece of hardware that decrements a counter every microsecond or similar. The control system's main loop, running on the CPU, "kicks" the watchdog by setting the counter to a value like 1000 each iteration. The result is that if the CPU fails to execute the main loop often enough, the watchdog will "fire". This a) tells you that you have a bug and b) typically reboots the system so it has a chance to recover.
Toyota used a timer service routine to kick the watchdog. This defeats the purpose of the watchdog. The control software can happily get stuck or crash and the watchdog will not notice. The fact that an engineer added this "feature" tells you that the watchdog was firing in development. That should have been addressed by fixing the buggy software, not by disabling the test.
The fact that the disabled watchdog made it into the production release is unforgivable.
<quote>
Spirit of C:
a. Trust the programmer.
b. Do not prevent the programmer from doing what needs to be done.
c. Keep the language small and simple.
d. Provide only one way to do an operation.
e. Make it fast, even if it is not guaranteed to be portable.
The C programming language serves a variety of markets including safety-critical systems and secure systems.
While advantageous for system level programming, facets (a) and (b) can be problematic for safety and security.
Consequently, the C11 revision added a new facet:
=> f. Make support for safety and security demonstrable.
</quote>
I don’t necessarily think it would, but if it did, those would all be reasons
There's also some really neat language-level concurrency support; work is ongoing on new features and a shorter summary, but you can see one of our Master's student theses for details: https://uwspace.uwaterloo.ca/handle/10012/12888
While C does have longjmp and friends, usage of them is hardly idiomatic, so most C code assumes no non-local tranfer of control happens when calling functions. Coding with non-local transfer of control and without require very different idioms.
C++ was a C variant once.
It doesn't require them/us to change much, just add these flags and the compiler will warn you about things that are unsafe.
I've always found it quite sad that no one is interested in bettering C enough to push relevant changes through.
Only newbies make memory corruption errors.
Yet even Dennis acknowledged correct code mattered, and Johnson created lint in 1979!
Largely ignored until clang and its analyzers came into the scene.
Other OSes not tied to UNIX culture were always more open to reach out for C++, even if constrained to a certain subset.
It can compile small enough to run on an Arduino: https://github.com/stepcut/idris-blink
OS X and its descendents is the only exception.
Which goes back to NeXTSTEP using Objective-C and offering UNIX compatibility only as a path to bring software into the system, battling against SGI and Sun market space.
http://www.l4ka.org/projects/pistachio/pistachio-whitepaper....
I can see your point if you're saying compiler use lets them avoid a language they just dont want to use. Which they couldnt if using it for an OS.
A safer variant wouldn't be C. What makes C great for OS development is that it is just a step above assembly and you as a developer are given tremendous amount of power to do good and evil. C#/Java are programming languages with training wheels and it's great for application development. But for low level coding required for OS, network stacks, databases, etc, you really have to take the training wheels off.
I suppose you can try and make the C type system more stringent, but then it wouldn't be C. And considering they are aiming for backwards compatibility with existing C and its immense code infrastructure, they will have to keep the "flaws" in c for all.
Time would be better spent making the libraries/kernel/etc sturdier but if they can pull it off and win the hearts and minds of OS developers, then so be it.
Also, people have been trying to sideline C for decades. Each attempt has only reinforced C's standing and reminded us why C is so essential for OS development. Anyone remember the ill-fated attempt by Sun with their JVM centered JavaOS?
I can switch to 4.6, but don't want the risk of an experimental feature yet.
Google is using languages with training wheels to write core components of Fucshia (TCP/IP stack and file system tools are written in Go), as well as the new Android GPU debugger (also in Go).
Although it's more along the lines of Plan9 - a unix-like system that ignores the bits of POSIX that really suck.
Also how would you make memcpy() safe in a POSIX implementation on Redox?
So it says Not Unix right on th tin.
Easy, don't have memcpy().
Something akin to Intel MPX.
I think the only successful "subset of C" is MISRA.
[1] http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.559...
For C programs, one strategy is to provide a set of macros to be used as replacements for unsafe types in variable declarations. These macros will allow you, with a compile-time directive, to switch between using the original unsafe C elements, or the compatible safe substitutes (which are C++ and require a C++ compiler).
The replacement of unsafe C types with the compatible substitute macros can be largely automated, and there is actually a nascent auto-translator[2] in the works. (Well, it's being a bit neglected at the moment :)
Custom conventions using macros to improve code quality are not that uncommon in organized C projects. Right? But this one can (optionally, theoretically) deliver complete memory safety. So you might imagine, for example, a linux distribution providing two build versions, where one is a little slower but memory safe.
[1] shameless plug: https://github.com/duneroadrunner/SaferCPlusPlus
[2] https://github.com/duneroadrunner/SaferCPlusPlus-AutoTransla...
Now will they in this.
* switch, if, choose and case extensions look good.
* I can see the justification for labelled break/continue, but looks pretty hairy. Might discourage rethinking and refactoring to something simpler.
* I'm wary of exceptions.
* I don't like the 'with' clauses.
* Weird to add syntax just for mutexes, but they integrate concurrency/coroutines later, so maybe it make sense.
* Tuples are generally useful, but C11's unnamed structs are generally good enough, ie. instead of [int, char] you can return "struct { int x0; char x1 }" or something.
* New declaration syntax is welcome, but the old syntax probably isn't going away, so I'm not sure it's a good idea.
* Constructors/destructors are good. Syntax looks weird though.
* Overloading is very welcome.
* Not sure about operators, but they have their uses.
* Polymorphism is welcome, though it looks a bit cumbersome, and it should come with a monomorphisation guarantee for C.
* Traits seem like too much for a C-like language. I can see the uses, and the compiler can optimize this well, but they're probably too powerful.
* Coroutines are cool.
* Streams look interesting, but the overloading of | will probably be confusing.
It seems like anonymous struct's fill the void, but a big problem with anonymous struct's is their types are never equal to any other, even if all the members are the exact same. So that means that if you declare the function as returning `struct { int x0; char x1; }` directly, it's actually mostly unusable because it's impossible to actually declare a variable with the same type as the return type. Obviously, the fix is to forward declare the `struct` ahead of time in a header file somewhere and then just use that type name, but that gets annoying really fast when you end-up with a lot of them. The tuples would allow you to achieve the same thing, but with a less verbose syntax and would allow them to be considered the same type even without forward declaring them.
Are you sure about that? I remember playing with this last year and structural equality seemed to work when returning structures from functions. I was using clang, so it could conceivably have been an extension... (edit: some online C compilers do indeed return an error in this case)
If that's the case, then just make anonymous structs employ structural type equality and you have better tuples.
> If that's the case, then just make anonymous structs employ structural type equality and you have better tuples.
Yeah, that would work, I'd be fine with that. I don't think it's quite as good as a dedicated syntax though, just because the `struct` syntax is a lot more verbose then a concise tuple syntax could be, and defining `struct`s inline is pretty clumsy.
At least anonymous structs would name the fields and so the type serves also as documentation.
Then K&R came, and took our nice toys away.
Fortunately, the GNU Pascal compiler needed nested subroutines, so they exposed the feature in their C compiler, as well.
On C++ we can fake them with lambdas.
Also, porting C is not that hard and does not require you to touch internals that much.
Everything standard was once non-standard ; if no one uses it it will never be standardised and we will be left with a poor status quo. For instance, there wouldn't be int8_t, etc... if people weren't using non-standard macros beforehand. Likewise for atomics, threads, etc.
Those extensions are useful and allow better portability across architectures. E.g. SIMD extensions is much better than writing two implementations with NEON and SSE intrinsics.
There is a LLVM talk about it.
However I do agree with you.
clang and gcc cover most of the systems that matter today and their C extensions definitely make C a safer language.
[0]: https://groups.google.com/forum/m/#!msg/boring-crypto/48qa1k...
Does anyone know if undefined behavior is specified in CompCert? Or does CompCert simply not allow you to write programs with undefined behavior?
I disagree. Dependencies, or coworkers, will use them despite your decision not to use them. When a dependency does use them, chances are the documentation is poor or non-existent.
"Recover no matter what" doesn't require exceptions. A common C idiom is to call a function like f(input, *err), where err points to memory where f can write error diagnostic info. Clunky, but I like how it makes the "exceptions" somewhat self-documented in the function signature.
Is "writing truly exception-safe" something that necessary ? for me, the biggest benefit of exceptions is that I can have some code throw from anywhere and display a nice error pop-up message to my user which informs me of what went wrong and revert what was currently happening during the processing of the current event, since the "top-level" event loop is wrapped in a try-catch block. Often enough, the user can then just resume doing whatever he was working on.
If you want your connections cleanly terminated, your temporary files removed, and your database transactions invalidated, yes.
sure, and if you develop in C++ and put these in RAII classes they will be automatically.
> Considering how hard it is to write truly exception-safe C++
that's the default behaviour in C++ code, how hard can it be ?
// This is unsafe.
sink( unique_ptr<widget>{new widget{}},
unique_ptr<gadget>{new gadget{}} );
// This is safe.
sink( make_unique<widget>(), make_unique<gadget>() );
[1] https://herbsutter.com/2013/05/29/gotw-89-solution-smart-poi...C11 is pretty nice! C99 is too. One might think that "almost once a decade" is kind of slow for updates, but M$ have enough trouble keeping up with the current schedule. Of course TFA describes a possible direction for C2x, but they could have a more charitable attitude...
That's one thing I'd like C to do. It was an adequate language in 1970, but in 2018, we have a few better approaches that have a chance to turn into viable alternatives to C.
Of course, I don't see C falling into disuse any time soon. The amount of critical code written in C is enormous, without a way in sight to reasonably replace. So keeping C in a good shape is important, whatever shortcomings the language may have.
In retrospect, I often jokingly refer to the 80s as “the x86/PC disaster”, an extinction level event for programming languages where the choices were to run either assembler, or C, on the IBM PC to build software of any size due to the limitations of the hardware.
Rather than trying to improve curly brace languages, it’s time to bury them.
For starters, hiding identifiers after arbitrarily long type expressions, instead of starting a line/block/expression with a name, is an un-fixable PITA. (Sorry, AT&T, Algol had it right)
Requiring a “break” in a case statement is a botch, instead of some kind of “or” / “set” / “range” test.
C is useful as a portable assembler, I guess. Otherwise I don’t really like any of C’s offspring all that much. I sort of like Javascript in spite of being forced to look like Java/C++, but that’s only because I learned some Lisp (alas, not Scheme) back in the day.
C compatibility is kept to the extent of ANSI C++ requirements.
ANSI C++14 requires C99 library compatibility and ANSI C++17 was updated for C11 library compatibility.
http://open-std.org/JTC1/SC22/WG21/docs/papers/2016/p0063r2....
They do put a great deal of work into the C++ compiler, and seem to be doing a way, way, better job than they were in the late 90s.
That may be the other reason they don't update the C compiler with new features.
C related improvements will only be done to the extent required by ANSI C++, or requests from key customers that might influence roadmap.
Anyone that really deeply wants to keep using C on Windows, and even enjoys using COM directly from C (the main ABI since Windows 7 and UWP core stack), can use any other C compiler.
In fact Microsoft has suggested clang multiple times, and has helped clang devs to make it work better on Windows.
-Vector types. (With arbitrary sizes not just up to vec4 for like on GPUs)
-New operators like clamp, and other intrinsic. (See GLSL and modern instruction sets)
-Min/max: |< >|
-Dot:
-Cross product: ><
-Swizzle: a.3.2.1.0
-Qualifiers for warping behavior.
-Standard library with cash management hints.
-Define qualifiers for padding of structs to be defined.
I would also argue you could change some things in the spec to make it easier for compilers to optimize, like making the calling a switch without a catching case undefined behavior.
As for the syntax itself, I could find being able to type multiple break commands in a row to get out of more then one loop useful, but its not a big thing.
I would probably drastically restrict the power of the pre-processor too.
(If you have too much time on your hands: https://www.youtube.com/watch?v=443UNeGrFoM)
[0] https://www.student.cs.uwaterloo.ca/~cs343/
I've always found it unfortunate that the university has courses from first year all the way to fourth year in Data Structures and Algorithms (all the way up to CS466/CS666) but Control Flow is treated like a secondary citizen.
As long as the language is small and has good tooling, and (most importantly) can easily interoperate with C libraries (have a look at Nim for an really awesome C integration), it doesn't matter whether it is backward compatible with C.
C itself should stay what it is. A low level and simple language without surprises which is only very slowly and carefully extended. Languages that are developed "by committee" and add lots of new features quickly are usually also quickly ruined.
1: https://www.reddit.com/r/programming/comments/7ugm8e/c2_c_wi...
> The purpose of the project is to engineer modern language features into C in an evolutionary rather than revolutionary way.
It's also the purpose of the standards body. Why not propose these changes to the standard?
And don't start with the stupid (u)intXX_t.
I wish more would.
Definitely. However, you have to admit the names (uint32_t) could be made less verbose, like "u32".
We are talking about the smallest possible datatypes that build up everything here. They don't have to be named variable_typeFactory_type_signed_t.
I use stdint.h (and inttypes.h since fuck printf without it amirite) but u8 ... are just better.
Personally I would much prefer uint16_t over u16. Confusion over type handling is a source of some of the worst bugs. Remember, uint16 isn't the same as 16 bits if you consider endian-ness.
Exponentiation Operator
New binary exponentiation operator '\' (backslash)
for integral and floating-point types.
2 \ 8u; // integral result (shifting), 256
-4 \ 3u; // integral result (multiplication), -64
I hope that’s a documentation error. Otherwise, it seems designed for the “Obfuscated C for all” contest: int i = f();
int j = i \ 3u;
Does what ‘\’ does really depend on the sign of i? x * 2 == x << 1
for any positive x. As far as I'm aware shifting a negative number is undefined, which will be why they use multiplication for negatives.If you're interested in working on the project though, more hands are always welcome; I'd suggest contacting our team lead, Peter Buhr, his email is pabuhr AT the university domain Cforall is hosted on.
When I started on the project ~3 years ago, it took me about 2 weeks to work around the (then current) set of compiler bugs to make a 100-line benchmark program -- naturally a public release at that point would not have been fruitful. Today our compiler generally works, and we're looking forward to making a public release once we get a couple more features stabilized.
Basically, day 1 transparency either requires a budget or misplaced priorities.
> I'm always skeptical of the claims that things can't be done transparently because it's not open to input yet. Those things are orthogonal.
You don't have to explain anything. You don't have to set up a website (case here already had a website, code was just hidden). I'm not sure where these requirements come from, but it's not true, and I'd argue you do more harm giving the impression of secret development than you do not accepting input at an early stage. We all understand the latter, but many of us are wary of the former when someone says their committed to openness and does the opposite.
Did they study existing code bases and bugs to find out how applicable these changes are to fixing real problems?
https://plg.uwaterloo.ca/~cforall/people
You can check the various Master Theses at the bottom of the page.
Efforts like those of the poster don’t really address these things.
When I tell people I like C, the response differs depending on experience level. Less experienced people who haven’t spent time in C++ either will say “Gross, pointers”. People used to C++ will say “what about templates and constructors/destructors”, and “are you going to write your own library for vectors and maps every time?” And people who are experienced (more experienced than me) and like C have generally said that the main things they miss from C++ are constructors and destructors (and mostly destructors), and templates (but only for container types), but that they can live without them.
C is a small language that can be relatively easy to write (once you pick/write a suitable stl-equivalent), and extremely easy to read (and to the extent that you can look at a line and know exactly what’s going on under the hood). IMO it should stay that way.
Take a look at zproject [0] for what I think is a good effort to standardize C style and project structure. Even if you disagree with the particular design choices, the spirit of it is what I think is needed to keep people from just assuming C is for dinosaurs.
I don't just have to worry about the problem that I'm trying to solve but also about handling the memory properly.
Of course there are scenarios where writing a fast and lightweight app is part of the problem but for many projects that's not the case.
I'm fine with pointer arithmetic and all that but I'm not fine with the constant fear that I did some oversight and my app is going to crash and I'll have to spend time debugging what's going on.
So I interpret your comment as something like "I don't want to program, I just want to assemble mathematical statements", which is a fine and perfectly legitimate thing to do, but it also seems a bit lazy, impractical, and probably irrelevant, given the subject.
Also I'm talking about projects (e.g. building a word processor), not programming problemns (e.g. invert this binary tree). In my experience projects are complex systems with many moving parts interacting and most bugs comes from the complexity of the system/problem. With C, I don't just have to think about the problem that I'm solving but if I did something wrong when dealing with memory allocation/releasing.
I don't have much experience in C except from the basics so I hope to hear from people that loves C why they choose it or if its just a constraint inherent to the problem they're dealing with.
Linux is pretty much deployed on a lot of hardware, it is still maintained, and it uses C.
Granted it is not the best language, but it's simple enough to do kernel development.
The same goes for cobol, it's the best paying language out there.
Who are these people[2], and what is their mandate? Did they actually consult with the likes of Linus Torvalds, Greg KH, Mike Pall--those who actually use C every single day on mission-critical projects like OS kernels and virtual machines--and ask them what improvements to C they need?
1) http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1548.pdf
> The ability to write generic reusable programs, called polymorphism, is fundamental to advanced software engineering. I am interested in static type-systems providing polymorphism without the use of subtyping. The problem with subtyping is that it imposes significant restrictions on reuse when static type-checking is required. This work has resulted in a new dialect of C language, called Cforall, which adds parametric polymorphism and extensive overloading to provide a more general type-system.
Really? Lots of C code written today is written using C89/C90 or C99 std. I think if it's something that history tought us it is that C does _not_ need to be transformed into something else or we would already be happily using this "something else" today.
It's the one thing they immediately emphasize that their language isn't, and it's the one example they give for "disadvantages of multiple legacy design-choices".
Indeed, thanks to VLA, the stack can be used as a clean notation for raii. You just replace this ugly, but very common, construction
void f(int n)
{
float *x = malloc(n * sizeof*x);
// ...
free(x);
}
with this void f(int n)
{
float x[n];
// ...
}Fun fact from when we switched our implementation to use VLAs: if you call alloca() for stack memory instead, it doesn't work properly inside a loop, because it never releases the memory until the stack frame is exited.
I've found it quite easy to spin up parallel tasks in uC++ if I needed both C++ and easy-to-use parallelism, the language isn't formally supported but for small student projects its pretty kick ass, also the prof who built it is just quick email away.
So all I'm all in favour of C for All as an alumni, hopefully it enriches the existing curriculum.
Stuff that I'd like to C in an evolved C:
regexp engine as part of the language so you can do
while (buf = <>) if (buf =~ /some regexp/) puts(buf);
<> and <FD> from perl, just handy.Lists as a built in, with any element type and builtins like push, pop, shift, unshift, etc. Again, more perl goodness without all the @'s and $'s.
${expr} interpolation inside strings. Like shell, and it's handy.
At this point I'm just typing in the L language docs, so most of the stuff here:
http://www.little-lang.org/little.html
BTW - the logo for the language is a tip of the hat to the things that inspired it: the Tcl feather logo, the Perl camel logo, and "C" all bunched together. I'm very fond of that logo but not sure that anyone but me gets it.
Exception Handling
Oh no :(Personally, I would like to see better support for immutability/purity and more first-class functions (does C still insists on functions to be global in scope?).
Also, I think user-defined constructors/destructors are a bad idea. I generally found that any side effects that are done in constructors and destructors are a nasty source of bugs. It's almost always better to use a factory method (function), which makes user-defined constructor/destructor useless as a concept.
well, no. In languages with constructors, you can enforce invariants in your class and ensure that no object that does not respect the invariants exists in your system. If you only have factory methods, you can still construct objects "normally" then nothing prevents anyone from creating another object which does not respect them. Or you can hide your struct definition in an implementation file, but then you loose the ability to construct on the stack and have to dynamically allocate, which kills performance.
Actually, it's not enough. If you can mutate the class variables (attributes), then you can always create a class that doesn't have these invariants enforced. The only way that could completely prevent invariants in the class being disrespected would be to have a strong, possibly dependent, type system.
For example, consider a function (method) that takes two objects of the same class and creates another object of the same class based on the two. During the construction of the returned object, invariants can be broken, and if this function contains an error, it will return an invalid object.
This is especially problematic with the RAII pattern. Because resource acquisition can fail, you have to allow for "broken" objects to be returned, which represent the resource not being acquired. In functional programming, this is done with sum types (and in general way with Maybe, for instance).
I would say I am against RAII pattern, because it gives people a false sense of security, as you have to deal with the resource not being acquired anyway (either by returning object in incorrect state or by exception). But if you forfeit RAII, you might as well get rid of user constructors and use factory methods (and simple data constructors and sum types) everywhere for simplicity and consistency.
You can completely avoid it if the invariants are expressed as validation methods, and mutation of state isn't direct but through a mechanism initially updates shadow state and guarantees that validators are run and failed validations result in both the original state being preserved and an error being signalled, and only successful validation results in state updates.
A sufficiently robust type system is superior than this kind of runtime check in all kinds of ways, but is not the only way to avoid invalid state with mutable objects.
... but with RAII you can't have invalid objects (if you actually care about your invariants of course).
If you do ::
class my_positive_int {
int m_x{};
public:
my_class(int x) {
if(x < 0)
throw invariant_broken;
m_x = x;
}
int get() const { return m_x; }
void set(my_positive_int other) { m_x = other.m_x; }
};
then at no point in your program, cue undefined behaviour such as reinterpreting the bytes of your class as something else, can you have a my_positive_int with x < 0.How many people agree with this statement? OO is a legacy design choice or in other words: a bad design choice.
I agree with the statement, so do most modern languages like rust and golang but I wonder what do non language designers think? This statement was given as if it was obvious but many programmers who I work with still think OO is the greatest invention ever.
OO can _definitely_ be misused and I think the disconnect is that the traditional abuses of OO (deep class hierarchies, fragile base classes, etc) are associated to "oldschool" Java and C++ codebases. But if developers followed the "composition over inheritance" mantra back then, maybe this complaint wouldn't be so prevalent.
I pretty fluidly move between Typescript/C#/GoLang and after the initial hump in GoLang, I find I don't have much preference. Some code feels more clunky without "traditional" OO and some feels better. I've yet to really dive into traits in Rust but I'm guessing I'll feel the same way when I get around to it.
I don't think it's trendy if newer next generation languages like go rust and nim avoid OO. These experts who design languages know something.
Like I said, I regularly write Go and don't have trouble with it's interfaces and duck typing.
> These experts who design languages know something
What about the experts who bolted classes onto ECMAScript? I don't think it's black and white. It's like the static vs. dynamic debate.. there is pros and cons of each approach.
I think among many language designers starting a whole new language, the issue is indeed black and white.
There isn't such thing as the only true and single way of doing OOP.
Also without OOP, good luck doing GUI programming in C++. Even Motif and GTK+ are OOP based, in C, because immediate UIs suck for anything beyond simple game UIs.
More specifically, C++-style OO couples (1) in-memory layout of types, (2) behaviour of objects, (3) encapsulation boundaries, and (4) the hierarchical "is-a" relationship. In Cforall, (1) and (3) are handled by the usual C mechanisms of struct types and translation-unit boundaries [we go to great effort to maintain C-style separate compilation, though we will likely build a better namespace/module system at some point], (2) by traits, and (4) by a yet-to-be-implemented system of RTTI for specific "inherited" types [likely when the undergrad RA who started our exceptions implementation comes back as a Master's student this fall].
As you mention, many use-cases (including widgets in GUIs, but also abstract syntax trees in compilers) require some sort of heterogeneous collection type to work properly, and for this OO is the right model. We just don't want to force the entirety of that model on every use case, so we can just take traits when we want to write a polymorphic function over related types or an ADT like "Set" which requires its keys be sortable, or just take RTTI for exception handling, when the set of exception types we're handling is known statically, but we need to determine if the thrown exception inherits from one of the caught types.
[1] https://golang.org/doc/faq#Is_Go_an_object-oriented_language
It doesn't matter what libraries one uses, they don't remove the OOP model out of JavaScript.
Additionally all those react ideas already existed in Smalltalk as well.
How are _init(struct obj o) _cleanup(sturct o) not enough? Okay, except those rare cases when you need a _new(struct obj o).
(And maybe a vector and hashmap).
Anyone else having trouble accessing the site?
Really, they should introduce some kind of modules or namespaces. Of course, C++ already has namespaces and will have modules sooner or later...
Zig looks neat, BTW.