This has been tried many times before, and eventually all these attempts die a lonely death. Why use extensions anyway? If one desired the luxury of modern scripting languages, switch to C++, Rust, Go or one of the other alternatives the article mentions.
Because regardless how some of us might dislike C and its security related issues, the truth is that no one is ever going to rewrite UNIX systems in other language, nor the embedded systems where even C++ has issues gaining market share.
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 do not question the usefulness of C, I use it in my daily work. What I am saying is that most C developers that use the language day-in-day-out know quite well what they are doing, and don't need yet another non-standard way of writing the code. Safety is a good point, but the initiative doesn't even mention the word, and there is no reason to assume the C-for-All extension targets safety at all
Fair point, but changing the language won't change the amount of global state. and the associated complexity, nor will it make subsystem supervision work correctly. Changing the language will not prevent unbounded recursion or the associated stack overflows and subsystem failures. Changing the language will not fix mis-analysis of task switching overhead. And changing the language will not fix manufacturing issues with the PCBs.
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.
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.
Bad practices can carry over to any language. They’re clearly incompetent at using C, but that doesn’t mean that they won’t be incompetent in another language.
Are you referring to the "unintended acceleration" scandal from ~10 years ago? If so the NHTSA investigated[0] that and found no flaws in electronics. The problem was essentially people pushing the gas when they thought they were on the brakes. Pedal "misapplication" I think it's called in the report.
In that investigation they seemed to place the blame more on sticky pedals and floor mats, not operator error.
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.
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.
This is because trusting the programmer is fundamentally wrong, no matter the programming language. In any good development process the actual coding is the least amount of work - for a reason.
Okay, this makes me rethink my previous statement. There are two kinds of trust here: A low level programming language should not impose restrictions on the code that prevent the programmer from doing what needs to be done, even if it looks wrong. This is how I read the 'Spirit of C" that you quoted. And certain applications would be impossible to write without it. But you need a development process to make sure that your system does exactly the right thing. So the quote should read "trust the process" rather than "trust the programmer".
What makes you think that a safer C variant would win the hearts of UNIX kernels and embedded devs any more than C++ (which started as just a C variant).
[actually on the Cforall team] This is basically our pitch -- the last 30 years of language design features applied to a language that is not only source-compatible with C (like C++), but actually maintains the procedural paradigm of C (unlike C++) -- idiomatic C code should require minimal to no change to make it idiomatic Cforall code, and skilled C programmers should be able to learn the extension features orthogonally, rather than needing to figure out complex interactions between, say, templates and class inheritance. We are working in the same space as C++, and do draw inspiration from C++ where useful, but with the benefit of decades of watching C++ try things to see where the important features are.
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
the existence of exceptions seems to bely the idea of minimal changes to make idiomatic C code idiomatic Cforall code.
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.
Okay, so the creators of this safer C promising that it isn't going to grow to the same size as C++? I guess if they're promising that, that makes sense.
I highly suspect it's not that Unix C diehards are against the idea of touching C++—it's that they're against the idea of using anything but C. I don't think anything can win over those kernel developers.
Its the complexity of the language vs the benefits it provides. C has many pitfalls (UB) but its simple. IMO a language that could migrate C programmers would have dependent types and an effect system with a nice syntax.
Other OSes, not impregnated with UNIX culture, have a different view on C++'s use on kernel space.
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.
Even OS X's C++ usage is kind of a historical oddity. OPENSTEP drivers were written in ObjC for an API called DriverKit, but OS X/Darwin/xnu replaced that with the C++ IOKit. The rest of the kernel is still C. I wrote a comment a few years ago explaining why I think that decision was made:
> So if one finally manages to get a safer C variant that finally wins the hearts of UNIX kernels and embedded devs
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?
A full time C#, can confirm. It's an awesome business logic langauge, but the hot path eventually gets rewritten in a very C-like style, with all Linq, exceptions and allocations thrown out.
The only way to make C safe without losing performance would be to accompany your C code with a formal proof that it avoids undefined behavior, and use a compiler which refuses to compile the code if the formal proof doesn't validate.
Microsoft already proven twice that languages with training wheels can be used for writing OSes.
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).
Which means that minimal requirements to win kernel and embedded devs is to integrate well with the rest of the C ecosystem, including myriad of C compilers and to be really well suited for low level work. This excludes pretty much all ideas, but meta languages that produce C code. Might even be necessary to promote the language itself not as a new language, but as a meta preprocessor for C to avoid alienating developers. But realistically this is not feasible nor necessary. There are much more feasible ideas to improve safety, than forcing half of the world to learn a lot of new things and change.
The trouble with a "safer C variant" is that it must remove features, or at least more heavily constrain programs to a safer subset of the language. This makes it not backwards-compatible.
I think the only successful "subset of C" is MISRA.
I remember reading a paper from around 2007 that asserted that most of MISRA did not catch or significantly prevent major bugs in code, indeed it asserted that much of the standard was useless. I am failing to find it now, as I cannot remember what terms I used, and I am not at a library computer and therefore I cannot search behind paywalls beyond abstracts.
Cannot say that this is unexpected, but I was interested to find some papers, presumably these two: Assessing the Value of Coding Standards: An Empirical Study [1], Language subsetting in an industrial context: A comparison of misra c 1998 and misra c 2004 [2]
SaferCPlusPlus[1], for example, is a safe subset of C++ that has compatible safe substitutes for C++'s (and therefore C's) unsafe elements. So migrating existing C/C++ code generally just requires replacing variable declarations, not restructuring the code.
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.
STL, RTTI and Exceptions should be avoided on embedded platforms (talking about 8 bit µCs here). I've extensively used both templates and lambdas on 8 bit AVRs (both the Tiny and Mega series); actually, writing templated code for µCs is a great way to avoid overhead stemming from function pointers etc. while still having well maintainable code.
> Because regardless how some of us might dislike C and its security related issues, the truth is that no one is ever going to rewrite UNIX systems in other language
[actually on the Cforall team] Huawei has funded the project for the past couple years, the web server is just our university research group's web host, which we didn't expect to get this much traffic on (it wasn't one of us who put this page on HN). We've been running fairly low-profile for the moment, but should make a beta release later this summer.
IIRC [not the team lead] they approached us when they were looking for modernization options for their existing C codebases. From there it went through the usual university-corporate research partnership process.
GNU C is probably my favorite extension of C. There's a lot of good stuff in there. The vector extensions make it really easy to write platform agnostic SIMD code.
I love using nested functions when I have to write state machine code. It's a hella a lot better than the old school way using macros to do the same thing.
Please don't use GNU C, or any other non-standardized version of C. A huge part of the reason C that is so widespread is because it's a well defined standard implemented by many compilers for many platforms. GNU C is defined by its implementation, which is awful.
> Please don't use GNU C, or any other non-standardized version of C.
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.
I disagree with this. A lot of GNU C works on both, GCC and Clang which covers most platforms out there.
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.
Please do use GNU C if you're going to use C. The viral nature of the Linux kernel has forced GNU C to be an important de facto standard. Take advantage of that!
Why not ? If I don't care about portability because for example I'm writing a software that it's meant to be used only on Linux because it uses Linux specific libraries or system calls and I know that there gcc it's the standard I use the extension if they can simplify my code ?
Considering how hard it is to write truly exception-safe C++ and considering how major C++ code bases don't allow exceptions, adding exceptions to C does not seem like a good idea.
I've always liked the idea of djb's boringcc[0], except with different definitions of undefined based on what users were using C currently with. This would allow people to "upgrade" into boringcc with their current code bases. So with a single invocation of a compiler, you couldn't use more than one set of defined undefined behaviors.
I would love a gcc optimization level, like -Og which only applies optimizations that don't interfere with debugging information, where all undefined behavior is specified.
Does anyone know if undefined behavior is specified in CompCert? Or does CompCert simply not allow you to write programs with undefined behavior?
Whether exceptions are good or bad depends on what error handling strategy your product needs. For some software, it's better to try and recover no matter what. For others, complete failure is preferrable to operating with invalid state. Exceptions can be a blessing or a curse depending on what you need. Having them in your toolbox is certainly an advantage over having no choice.
>Having them in your toolbox is certainly an advantage over having no choice
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.
> Considering how hard it is to write truly exception-safe C++
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.
Exception safety is hardly a "default behavior" of C++, considering such gems[1] as:
// This is unsafe.
sink( unique_ptr<widget>{new widget{}},
unique_ptr<gadget>{new gadget{}} );
// This is safe.
sink( make_unique<widget>(), make_unique<gadget>() );
The default nowadays is basic exception safety, where nothing leaks but objects can get put in invalid states. Strong exception safety (rollback semantics) is still pretty hard.
I'm actually on the Cforall team -- we've been running fairly low-profile for the moment (it wasn't one of us that posted the homepage to HN), but plan on making a beta release of the compiler and stdlib sometime this summer.
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.
Thank you, I'm not able to provide a lot of help but I would like to follow the discussion, perhaps the sensible thing to do at this point is to wait for the first beta, then probably the mailing list will be open to everybody. Thank you for replying.
Most programming languages do an initial period of internal development before a public release.
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.
I think there is a difference between working on something in public and a public release. 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 work in secret just to avoid some of the issues with publicity. Granted I know it happens often, I just don't think it needs to be that way.
If you want to work on something in public, you potentially have to write public-facing prose. You have to explain what the project is, where to find stuff, who to contact, documentation... set up a website, wrangle newcomers, commenters, press, etc. For a research project, that's a lot of work to do by people with limited time/budget for something that hasn't even been demonstrated to work.
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.
If there's one thing I would do to C it is to replace the normal datatypes with u8, u16, u32, u64 (ad infinitum) and their signed brethren s8, s16, s32, s64, ...
And therein lies the issue. Everyone does just that, causing needless clashes. If you're writing a library, you can either switch back to the standard type names in your external header file, or you include the typedefs/macros there as well. The former choice ruins some of the point of having these types, and might even mask cross-platform issues. The latter runs the risk of clashing with the user's owns typedefs, or with another library writer's typedefs. (And god help you if exactly one library writer chose to do it via macros.)
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.
Personally I don't think actual EE's doing firmware care very much for C89/C90. But I've seen CS people totally spaz when confronted by code with variable length arrays. And try to 'fix' code by removing <stdint.h>
It's not meant as a slight against EEs. It's just my experience that people with EE backgrounds tend to be more invested in becoming very skilled with "ANSI" C (plus whatever extensions help them get things done) while people with CS backgrounds are more likely to be interested in chasing new and "better" ways of doing things like C99 VLAs or C11 generic selection or a new language. It seems like your experience has been the opposite of mine so maybe it's wrong for me to generalize.
* 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.
I'm more or less in agreement, but I just though it was worth adding that the tuple's could actually have a lot of merit, I think I'd like to see them (Though I'm not sure the syntax is perfect parsing wise. It might be smart to prefix them, like `tuple [int, char]` or something.).
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.
> 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.
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.
`gcc` definitely throws an error. It tells you something like "struct <anonymous> was expected but struct <anonymous> was found". It's a pretty fantastic error message /s
> 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.
Yes it's more verbose, but avoids adding new primitive to the language for something that is probably not too common. I'm also not a fan of tuples because the fields aren't named. I mean, which field in a return type of [int, int] do I want exactly?
At least anonymous structs would name the fields and so the type serves also as documentation.
"Without continued development of the language, C will be unable to cope with the needs of modern programming problems and programmers; as a result, it will fade into disuse. Considering the large body of existing C code and programmers, there is significant impetus to ensure C is transformed into a modern programming language."
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.
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.
> 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.
> 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
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.
> 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.
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.
> 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).
... but with RAII you can't have invalid objects (if you actually care about your invariants of course).
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.
Agree 100%. Improvements to C would be things like removing "undefined behavior", not adding more syntax sugar. If anything, C's grammar is already too bloated. (I'm looking at you, function pointer type declarations inside anonymized unions inside parameter definition lists.)
> Improvements to C would be things like removing "undefined behavior"
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 think many people overlook that "undefined behavior" mainly limits portability. Invoking undefined behavior won't cause your program to do random things, it just might not do the same thing when you switch to a different compiler. This is still a big problem, but not as big as people make it out to be, in my opinion. I think undefined behavior problems in the standard could be fixed, but compilers would need special flags for backwards compatibility with programs that rely on it.
No that's implementation-defined. Undefined means code that is logically incorrect, but too expensive for the compiler/runtime to check and handle (reject) it. A simple example is array out of bounds access. It's too expensive to demand the compiler prevent it or guarantee to trigger some kind of signal at runtime. But if you are willing to pay the cost , you can use a language or compiler that is safer and promises to throw an exception or provides safe statically sizd arrays
> Invoking undefined behavior won't cause your program to do random things
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.
Undefined behavior is undefined even using the same version of the same compiler. If it does produce the same repeatable behavior, consider it luck.
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.
Implementation defined behavior is not the same thing as undefined behavior.
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.
There's also behavior which is undefined for hardware reasons.
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.
You're mixing up "undefined behavior" and "implementation-defined" behavior. Implementation-defined behavior is fine. "Undefined behavior", as the term is used in the C spec, means that literally anything can happen. The compiler is allowed to assume that UB never happens, so if it does the program can produce random results.
> You're mixing up "undefined behavior" and "implementation-defined" behavior. Implementation-defined behavior is fine. "Undefined behavior", as the term is used in the C spec, means that literally anything can happen.
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.
> 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.
Implementation defined behavior does the same thing for a given implementation. The compiler can produce totally different code every time you compile a program with UB. It can even affect code that is totally unrelated to the UB.
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....
> the whole point of the standards specifying UB is precisely to let implementations define the behavior themselves.
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.
This is the sort of emotionally-charged fearmongering around UB that really makes any discussion pointless. That example is wrong. Integers can be signed. If a compiler cannot prove x >= 0, then it simply cannot remove that code.
Now, if you used
unsigned int x = whatever;
...
if(x < 0)
There would be an obvious case for removing that if.
A very simple test case demonstrates that GCC can remove tests in the presence of signed overflow, even in ways that change a program's behavior.
$ 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
Yes, that example is well-known but different; here, the compiler is assuming that x + 1 will always be greater than x, which is entirely something else than the parent's assertion of assuming that x + small_increment will always be positive.
The difference doesn't matter, you would know better if you weren't clinging so hard to your beliefs. Here's the "difference":
$ 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!
Your "small_increment" needs to be at least large enough to turn the smallest negative int into a non-negative number, and that makes it unrepresentable as a signed int itself.
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.
> "perform UB, then check whether it happened" doesn't actually work
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.
That's not the point, though. In the case of undefined behavior, a compiler doesn't have to define the behavior or even act consistently (most evident by the behavior of the compiler's optimizer).
This is entirely different than implementation defined in that a conforming compiler has to document the behavior they implement and do it consistently.
When people talk about "removing undefined behavior", they usually mean requiring that compile-time-detectible undefined behaviors be converted into explicit errors.
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.)
> compilers already do that ( yeah, it's one of those RTFM things)
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.
...because, well, the behavior of integer overflow is undefined in C, so that code could technically do anything
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.
It's not about the compiler doing "extra checks" to deliberately do something different. The real issue is with aggressive optimizations.
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.)
Compilers deciding to elide code paths that contain undefined behavior is weird, especially when it chooses to silently elide your checks for division by zero or overflow. It's not weird that actually dividing by zero can do anything; it's weird that by having a possible division by zero can allow the compiler to decide that (divisor==0) is false and ignore it.
> 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
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!)
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/
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?
You are conflating undefined with implementation defined. If you use undefined behavior the compiler might simply delete your code since it's not defined.
You and a lot of people in this discussion seem confused the other way. The amount of things in C that are implementation-defined is relatively small and most has to do with character encodings and byte size (storage unit). The kind of behavior people are pointing to is definitely labeled as undefined, and there is a lot of it. Take a look at Harbison and Steele for example.
I've noticed the same thing. A strongly adversarial relationship between compiler writers and users, justified by religious adherence to The Holy Standard and a complete lack of understanding of how people actually want and expect the language to behave in practice.
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.
I'll just refer you to a comment I wrote last week, which I covers a specific case where undefined behavior and how a compiler chose to handle it caused a security problem in Cap'N'Proto.[1] I think it's about as condensed as I've seen it explained, while linking to further information.
Can you explain why exceptions and operator overloading are "idiotic" things? Are you from the Go school of boilerplate-error-checking-code design, or something?
exceptions because in embedded contexts they may not always be a good idea (and C targets such contexts). overloading because it is too easy to abuse and as such it gets abused a lot by those who do not know better. The rest of us are then stuck decoding what the hell "operator +" means when applied to a "serial port driver" object
> Reasoning about exceptional control flow w.r.t. manual memory management is a total nightmare.
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.
C does not have RAII-like memory management in any way. Exceptions work beautifully with memory management like that, but if it's not there, you can't just say it should work because memory management should work like that.
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?
Clang harks back to 2007 and LLVM 2003.. is this a research project that was recently taken back up?
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.
The initial implementation was finished in '03, and we revived the project somewhere around '15, so your guess about a research project that was recently taken back up is correct.
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.
It's maybe not quite what you're looking for, but Cforall's polymorphic functions can eliminate nearly-all the unsafety of void-pointer-based polymorphism at little-to-no extra runtime cost (in fact, microbenchmarks in our as-yet-unpublished paper show speedup over void-pointer-based C in most cases due to more efficient generic type layout). As an example:
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 type
Excuse me for my lamerism, but can you tell me what is a polymorphic function?
My 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?
A polymorphic function is one that can operate on different types[1]. You would maybe be familiar with them as template functions in C++, though where C++ compiles different versions of the template functions based on the parameters, we pass extra implicit parameters. The example above translates to something like the following in pure C:
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 RAII-like memory management in any way.
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.
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-initialize
Constructs like closures come at a cost. Function call abstraction and locality means hardware cannot easily prefetch, instruction cache misses, data cache misses, memory copying, basically, a lot of the slowness you see in dynamic languages. The point of C is to map as close to hardware as possible, so unless these constructs are free, better off without them and sticking to what CPUs can actually run at full speed.
Closures are logical abstractions and cost nothing, since they are logical. Naive runtime implementations of closures can of course be a bit slower than native functions, but so can be everything.
Clousure costs a lot if we are talking of real closures, that capture variables from the scope where they are defined, because you need to save somewhere that information, so you need to alloc an object with all the complexity associated.
And 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.
If a closure cannot be optimized out, i.e. the scope of the closure outlives the scope of the function it captures a variable from, than this closure is equivalent to a heap allocated struct, which cannot be allocated on the stack either if it outlives its scope. So the cost is still the same.
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.
> 3) is missing a few real improvements (closures, although it is not clear whether the "nested routines" can be returned)
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).
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?
Maybe because one needs to buy a seat at the table, by making a formal proposal, pay the travel expenses on their own and get to win the hearts of others members when voting comes to be.
Without continued development of the language, C will be unable to cope with the needs of modern programming problems and programmers; as a result, it will fade into disuse.
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.
Indeed. Having started my CS degree work in the early 80s, I got to see “things other than C/C++”.
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.
Herb Sutter doesn't like C. That may be the reason that the compiler group at Microsoft don't put any effort into updating the C compiler to support newer features.
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.
The reason is official and has been communicated many times.
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.
[on the Cforall team] It broadly matches our other operator-overloading syntax, where the ?'s show where the arguments go, e.g. ?+? for binary addition, ?++ for postincrement and ++? for preincrement. For something as common as constructors and destructors, a concise syntax is a desirable feature.
Wow, they really hate object-oriented programming.
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".
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.
I've written in the past about C2[1]. Nim is aimed at bit of a higher level than a "c replacement" should. I don't really know much about zig, but from what I do know, I like it.
Peter Buhr also teaches CS 343: Concurrent and Parallel Programming [0] at the University of Waterloo in a dialect of C++ that he has been working on [1], called uC++ [2].
[on the Cforall team] One of our Master's students has incorporated the majority of the uC++ features into Cforall as well, with some neat extensions for multi-monitor locking in a way that doesn't introduce synchronization deadlocks not present in user code.
As an alumni, I firmly believe there's room for a 4th year course in-addition to CS343. There's quite a lot in advanced control flow which can be covered.
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.
[also a CS 343 TA] I personally agree with you -- if it were up to me I'd refactor CS 343 into a pair of courses, maybe focusing on high-level concurrency constructs with a follow-up course on building that sort of runtime system.
I personally really hated the use of uC++ and would have loved to do the whole course in some MIPS dialect. I really liked how through most of second year the only language reference I really needed to look at fit on one piece of paper (https://www.student.cs.uwaterloo.ca/~cs241/mips/mipsref.pdf). The uC++ language, on the other hand, is not specified anywhere except in the enormous 600 page textbook, and even then very far from fully specified (e.g. there was a builtin function called rendezvous-something, where that string literally only appeared in the book once, and it was not defined at that place)
I'd argue that's the point of moving from a 2nd year course to a 3rd year one. You incrementally add more complexity. The ISO C++ spec is a similar heavy tome.
We already have this. Just use C++ and stay away from the features you don't want. If you want to enforce specific restrictions then start an effort to provide a formal C++ feature restriction specification.
uWaterloo uses an in-house language to teach advanced control flow, in my days it was uC++ built by Peter Buhr in CS343. They might have opted to use C for All now.
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.
[on the Cforall team -- also a CS 343 TA] We've incorporated the main uC++ feature set into Cforall, but are still using uC++ for 343; maybe in another couple years when Cforall is stabilized a bit we'll give it to the students.
> 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.
320 comments
[ 1.8 ms ] story [ 310 ms ] threadSo 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>
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2139.pdf
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.
https://news.ycombinator.com/item?id=10006411
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.
https://github.com/kframework/c-semantics
http://fsl.cs.illinois.edu/pubs/ellison-rosu-2012-popl.pdf
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...
[2] http://leshatton.org/Documents/MISRA_comp_1105.pdf
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.
Zig looks neat, BTW.
https://gcc.gnu.org/onlinedocs/gcc/C-Extensions.html
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 ?
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.
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.
* 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.
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.
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 ::
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.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.
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.
See http://en.cppreference.com/w/cpp/language/ub
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:
Here's what the compiler thinks: Then the compiler simply deletes your security check: 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
There would be an obvious case for removing that if.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:
...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.
1: https://news.ycombinator.com/item?id=16596409
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.
My 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?
[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.
[1] https://github.com/vsedach/Thinlisp-1.1
[2] https://www.ravenbrook.com/project/mps/
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?
And 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:
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:
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...
> 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?
[1] - https://www.iso.org/standard/57853.html
Anyone else having trouble accessing the site?
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.
How are _init(struct obj o) _cleanup(sturct o) not enough? Okay, except those rare cases when you need a _new(struct obj o).
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".
- http://c2lang.org/site/
- https://ziglang.org/
- https://nim-lang.org/
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...
[0] https://www.student.cs.uwaterloo.ca/~cs343/
[1] https://github.com/pabuhr/uCPP
[2] https://en.wikipedia.org/wiki/%CE%9CC%2B%2B
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.
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.
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.
> 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.