Tell HN: C Experts Panel – Ask us anything about C

829 points by rseacord ↗ HN
Hi HN,

We are members of the C Standard Committee and associated C experts, who have collaborated on a new book called Effective C, which was discussed recently here: https://news.ycombinator.com/item?id=22716068. After that thread, dang invited me to do an AMA and I invited my colleagues so we upgraded it to an AUA. Ask us about C programming, the C Standard or C standardization, undefined Behavior, and anything C-related!

The book is still forthcoming, but it's available for pre-order and early access from No Starch Press: https://nostarch.com/Effective_C.

Here's who we are:

rseacord - Robert C. Seacord is a Technical Director at NCC Group, and author of the new book by No Starch Press “Effective C: An Introduction to Professional C Programming” and C Standards Committee (WG14) Expert.

AaronBallman - Aaron Ballman is a compiler frontend engineer for GrammaTech, Inc. and works primarily on the static analysis tool, CodeSonar. He is also a frontend maintainer for Clang, a popular open source compiler for C, C++, and other languages. Aaron is an expert for the JTC1/SC22/WG14 C programming language and JTC1/SC22/WG21 C++ programming language standards committees and is a chapter author for Effective C.

msebor - Martin Sebor is Principal Engineer at Red Hat and expert for the JTC1/SC22/WG14 C programming language and JTC1/SC22/WG21 C++ programming language standards committees and the official Technical Reviewer for Effective C.

DougGwyn - Douglas Gwyn is Emeritus at US Army Research Laboratory and Member Emeritus for the JTC1/SC22/WG14 C programming language and a major contributor to Effective C.

pascal_cuoq - Pascal Cuoq is the Chief Scientist at TrustInSoft and co-inventor of the Frama-C technology. Pascal was a reviewer for Effective C and author of a foreword part.

NickDunn - Nick Dunn is a Principal Security Consultant at NCC Group, ethical hacker, software security tester, code reviewer, and major contributor to Effective C.

Fire away with your questions and comments about C!

987 comments

[ 2.8 ms ] story [ 432 ms ] thread
I'll pass! Thanks though.
Please don't do this here.
(comment deleted)
(comment deleted)
Some simple instructions about how to use a thread for conversation would be appreciated. Thanks!
Nothing to it! Just hit the reply button on comments you want to respond to. You can also upvote anything you like by clicking on the up arrow to the left of the comment.
Okay, is there a starting thread for today's C Experts panel? I miss the old net newsgroups.
The thread is https://news.ycombinator.com/item?id=22865357, which is the page you've been posting to. It's now listed on the front page of the forum, https://news.ycombinator.com/, which is a list of the stories people have upvoted today.

You're not the only person who misses the old newsgroups! The format that Hacker News uses is one that became sort of standard on the web in the early 2000s. It works differently than usenet did, but you get threaded comments in the sense that replies are nested under the posts they're replying to.

I've been waiting for a book on C from No Starch Press, so I'm really excited for this one.

This might not be too deep a question on the C language in regards to this book, but I've been wondering, why did you decide to have an eldritch horror as the book's cover?

It's a longish story, but people do seem to like the cover. We started equating the idea of C == Sea, so we had some early drawings of the robot riding various undersea creatures including a giant squid. I thought that looked overly phallic, so I suggested the robot ride Cthulhu instead, an unofficial mascot of NCC Group.
I like how Cthulhu is shown as kind of a guide for the robot.

The C==Sea brings to mind the book Expert C Programming: Deep C Secrets.

Wait, they put Cthulhu on the cover of a programming book? I'm buying it.
So what do people think about having a feature in the C language akin to the defer statement in GoLang?

The GoLang defer statement defers the execution of a function until the surrounding function returns. The deferred call's arguments are evaluated immediately, but the function call is not executed until the surrounding function returns. It looks like an interesting mechanism for cleaning up resources.

(comment deleted)
It could be very useful for cleaning resources. I've never used GoLang, but can see how that could be useful in various circumstances. As we're talking about C, I suspect a feature like that, with the potential to make things safer, would also enable the unwary to shoot themselves in the foot more easily.
I personally don't like golang's defer. For me it obscures the flow of the program. For example when I acquire a lock, I like to see where exactly it's released.

For me "defer" only makes sense in the context of exceptions, basically as an equivalent to "finally". This is a slippery slope though, since golang's exceptions are, for a reason, rudimentary.

How about deferring until the surrounding block scope ends? In Go you can get around the limitation of defer only executing at the end of a function by wrapping any arbitrary section of code inside an immediately executed anonymous function. But in C I'm not sure that's possible so maybe one could declare a new block scope instead to control when defer kicks in.
It sounds like the __attribute__((cleanup(…))) already offered by GCC is similar to this. I probably won't have time to investigate the differences while the AMA is ongoing though.
I would love to see defer in the language. It helps keep cleanup code close to the resource that is acquired.

Would the proposed defer statement apply to loops as well? How would one implement such defers without dynamic allocation?

How and why will C combat Rust?
In my opinion, the two languages are going to co-exist for a long time. C has billions of lines of legacy software written in it… In recent news, COBOL developers were sought after in order to update existing COBOL software, so the same thing will happen with C, perhaps to the end of humanity (I have become pessimistic as to humanity's future).

There are pieces of software that should be given priority for a rewrite in Rust, but most of C software is never going to be rewritten, because there is simply too much of it.

Therefore, even if C did not have any advantage of its own over Rust, there would still be legacy software to maintain and to extend.

The advantages of C include that sometimes, an embedded processor with a proprietary instruction set is provided by the chipmaker with its own C compiler, which is the only compiler supporting the instruction set; that C is still currently used to write the runtimes of higher-level languages (I'm familiar with OCaml, but it isn't too much of a stretch to imagine that the runtimes of Python, Haskell,… are also written in C).

There's tons of legacy C around, we have to maintain it, it's not ideal unless you're on some niche platform, lots of stuff should probably be written in a better language . . .

I sincerely hope this is not the general attitude of the standards committee. Some of us actually prefer C, and would like to see the language continue to flourish.

Note that among the C experts participating in this AMA, I am not one who is in the standardization committee. At 14:59 EDT, just before the AMA was posted, we were joking between ourselves about me having to post this disclaimer but I guess there was a hidden truth in the joke.
> In my opinion, the two languages are going to co-exist for a long time.

It goes deeper than that, in a couple of places Rust depends on the C standard: the fixed-layout `#[repr(C)]` structs (without that attribute, the compiler is free to reorder the struct fields; with that attribute, it's laid out the way C would do it), and the `extern "C"` function call ABI. The way to call any other language from Rust, or Rust from any other language, is to go through `extern "C"` functions passing `#[repr(C)]` structs. So even if the C language dies one day, parts of it will live in Rust forever (or as long as the Rust language lives).

C is a pretty well established language, so this question should probably be asked the other way around. C was primarily designed to complete with FORTRAN.
Rust has a package manager while C and C++ don't (as far as I now). This alone make Rust more attractive for some projects. I hope C and C++ get one.
When you're looking at an unfamiliar C code base for the first time, how do you approach it? Which files do you look for? Which tools to you open up immediately?
It all depends on how organized previous workers were, and what your goal is for a modification of the source text. Often, headers (dot-h files) document the data structures and interfaces.
cscope can help
Yes, I have found it helpful. One nice feature is that it uses a character-terminal interface, not a platform-specific GUI.
Is there a vim-style cscope interface for emacs? I hate that xcscope brings up its own persistent buffers (replacing other buffers that I had deliberately placed on the screen). Vim, conveniently, just pops up the cscope interface when I need to enter some input, and then hides it away. Also I don't think xcscope works with evil's tag stack whereas in vim, I believe, you can just return to where you were with ^T, whether using ctags or cscope.
This depends a bunch on what your goals are. There are no specially named files, so looking for a particular filename is not particularly useful. It is sometimes informative to find the file containing the main, but not always.

My job at NCC Group involves a lot of code reviews, so frequently the files that are of interest to me are the ones that contain the most defects. I typically identify these by compiling with compiler warnings turned up and warning suppression turned down. I'll frequently also make use of static and dynamic analysis, including the GCC and Clang sanitizers.

I start with generating tags.

  exctags --exclude=TAGS --exclude=TAGS.NEW --append -R -f TAGS.NEW --sort=yes  && mv TAGS.NEW TAGS
My editor (vim) has native support for quickly jumping from a use to definition via this TAGS index. History is preserved (i.e., there is a "back" button), so you can quickly dive through 5 layers of API and back out to understand where a value went. It is quite useful for starting with what you know and following it to the surprising behavior, without executing the code.
Are any concurrency primitives planned for introduction in future C revisions?
We currently have not seen papers proposing to add new concurrency primitives for C2x, but we have been actively working on the concurrency object model and would welcome proposals for new primitives or concurrency-related fixes.

One goal is to re-unify C with the concurrency object model used by C++ to make std::atomic<T> and _Atomic(T) be ABI compatible as intended in C11. Some small fixes in this area are the removal of ATOMIC_VAR_INIT, clarifying whether library functions can use thread_local storage for internal state, and things along those lines. However, we expect there to be more efforts in this area as we progress the standard.

C11 has seen new features, such as Generic Selection. Is the current language standardization converging (just adding clarifications, removing the surface for undefined behavior, etc.) or is C still growing with new features?

In other words, will the C standard be effectively “done” at some time in the future?

I would say no, that we are still adding new features. Aaron Ballman was responsible for adding attributes to the C2x (he can tell you more). We're also looking at #embed feature to incorporate binaries the way that #include incorporates text.
Fixing minor bugs or inconsistencies and reducing the number and kinds of instances of undefined behavior are some of the efforts keeping the C committee busy.

Reviewing proposals to incorporate features supported by common implementations is another.

Aligning with other standards (e.g., floating point) and improving compatibility with others (C++) is yet another.

In general, when an ISO standard is done it essentially becomes dead. So for the C standard to continue to be active (on ISO's books) it needs to evolve.

It's interesting to hear the standardization perspective, because it's pretty much the opposite of my perspective as a user.

I see the classic path of any programming language -- regardless of standardization -- is to continuously add features until it's too big and complex that nobody wants to deal with it any more. Then it's replaced by a newer, simpler language that takes the important bits and drops the unnecessary complexities. At that point, everybody sees that the older language was barking up the wrong tree, and they stop wasting time on it.

It's not the cessation of language change that causes language death -- that's merely a symptom. You can't keep a language alive simply by changing it every year. Some people sure have tried.

Alternatively, until it's evolved so much that there is so much diversity of implementation that simply knowing a library is written in "language X" doesn't tell me much about how it's written, or whether I can use it in my program which is also written in "language X".

Then again, C is the exception to every rule, so maybe we can keep piling on features indefinitely, and people will have to use it (even if they don't like it), for the same reason they started using it decades ago (even if we didn't like it).

Can memory safety be ensured in the C programming language? By static analysis at compile time for example?
(comment deleted)
It is possible to guarantee that a C program does not have any undefined behavior, which includes all the memory errors that are often also security vulnerabilities.

“Static analysis” may be the wrong name to classify the tools that work in that area, because “static analysis” is usually used for purely automatic tools, whereas the tools used to guarantee the absence of undefined behaviors are not entirely automatic except for the simplest of programs.

Results of a static analyzer are often characterized in terms of “false positives” and “false negatives”. It is a possible design choice to make an analyzer with no false negatives. It is absolutely not impossible! (Some people think it is fundamentally impossible because it sounds like a computer science theorem, but it isn't one. The theorem would apply if one intended to make an analyzer with no false positives and no false negatives—and if computers were Turing machines.)

Analyzers designed to have no false positives are called “sound”. In practice, this kind of analyzer may prove that a simple program is free of Undefined Behavior if the program is a simple example of 100 lines, but for a more realistic software component of at least a few thousand lines, the result will be obtained after a collaborative human-analyzer process (in which the analyzer catches reasoning errors made the human, so the result is still better than what you can get with code reviews alone).

Here is what the result of this collaborative human-analyzer process may look like for a library as cleanly designed and self-contained as Mbed TLS (formerly PolarSSL): https://trust-in-soft.com/polarSSL_demo.pdf?

Will C eventually get something like C++' constexpr?
C has some basic support for constant expressions already, but there has not yet been a proposal to bring 'constexpr' over from C++. Personally, I would love this feature to be in C!
You and me both!
This is really the only thing that I really want from C++. It would be amazing if this could make the cut for a future spec.

EDIT: I work on embedded systems, where C is king, and it seems like a spend an inordinate amount of time working with code generators that build simple tables. All of which could go away with this feature.

Why do you want it in C? What are your use cases?

I’ve always thought that idiomatic C for constexpr would be to write the code you want to be executed at compile-time in a separate file(s), build it, execute and then #include the result in your program before building the final executable, adding a build step but keeping overall complexity minimal.

This is different from C++ approach, where everything and the kitchen sink is added to the standard and then you have to issue errata for errata for the standard and hope that the compiler you have to use for your current platform is keeping up for the last changes.

1. What is the easiest way to build cross-platform (native) GUI with C?

2. Why it is harder to find lgpl licenced libraries to access windows directories over network like jcifs pysmb (and libraries overall) when needed to close most part of software source to sell small softwares to businesses?

3. If you needed to combo C with another language to do everything you need to do forever and never look back what other language would that be?

What's up with `strlcpy` and `strlcat`? Are they getting standardized?
No one has proposed making these standard. I doubt they would gain much support as they are similar to the Annex K Bounds Checked Interface functions strcpy_s and strcat_s but not quite as good IMHO.
There were a number of recent proposals to adopt various POSIX functions by Martin Sebor into C including:

  N2353 2019/03/17 Sebor, Add strdup and strndup to C2X
  N2352 2019/03/17 Sebor, Add stpcpy, and stpncpy to C2X
  N2351 2019/03/17 Sebor, Add strnlen to C2X
He is lurking on this thread as well. These proposals can all be found in the document log at http://www.open-std.org/jtc1/sc22/wg14/www/wg14_document_log...
The results (from the minutes http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2377.pdf)

6.33 Sebor, Add strnlen to C2X [N 2351] Result: No consensus on putting N2351 into C2X.

6.34 Sebor, Add stpcpy, and stpncpy to C2X [N 2352] Result: No consensus to put N2352 into C2X.

6.35 Sebor, Add strdup and strndup to C2X [N 2353] Result: N2353 be put into C2X. The committee wants a proposal for the wide character versions of any POSIX functions voted in this meeting.

There have been some disagreements on strlcpy/strlcat (BSD vs glibc crowd), although by now the debate has died off and these functions are pretty widely used. Also, while here, it would be lovely to have strchrnul() included.
glibc still refuses to add the functions because they are not required by a standard.
> similar to the Annex K Bounds Checked Interface functions strcpy_s and strcat_s but not quite as good IMHO.

Err... I thought Annex K is deprecated and dead? Whereas strl* seem very much alive, some compilers even give a "strcpy/strncpy is unsafe, use strlcpy instead" warning.

FWIW, Annex K is not currently deprecated.
It's not commonly available though, e.g. on Linux/BSD systems...
Correct -- it would be nice if the glibc maintainers would reconsider their opinion of supporting the optional Annex K functionality. There is definitely user demand for the feature.
And every BSD out there. And whatever it is that macOS does. Microsoft looks to be the outlier to me.
Microsoft does not even implement Annex K.

> Microsoft Visual Studio implements an early version of the APIs. However, the implementation is incomplete and conforms neither to C11 nor to the original TR 24731-1.

> As a result of the numerous deviations from the specification the Microsoft implementation cannot be considered conforming or portable.

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1967.htm

> rseacord 22 minutes ago [-]

> The C Committee has taken two votes on this, and in each case, the committee has been equally divided. Without a consensus to change the standard, the status quo wins.

The fact that it has only survived on status quo is a pretty crass hint that things aren't well with Annex K.

It should be.
Well, I am informally proposing making those standard :-).

IMO they're a lot more ergonomic than the Annex K functions, and do the thing most programmers think the strncat/strncpy functions do (admittedly, not part of ISO C).

Annex K should be forgotten as the mistake it is and we can move on with existing real-world interfaces instead of inventing features from whole-cloth. I thought that was generally the C standard operating practice.

We've been considering proposals to add common POSIX APIs into C, but I don't believe we've seen a proposal for strlcpy or strlcat yet. I recall we agreed to add strdup to C given its wide availability and usage.
There are deficiencies in almost all proposals. Two new functions which avoid the problems are supposed to be published in C202x: strcasecmp and strncasecmp, added in header strings.h (note: not string.h).
strdup seems like a perfect example of "standardizing existing practice." And it has never struck me as running against the spirit of C.
In fact I proposed strdup on a few occasions, but it wasn't adopted. It seems that they didn't like for standard library functions to use malloc. POSIX.1 specifies strdup.
I hope not, they perform much worse than they need to :(
Is it worth it to learn C in 2020 ? Will it still be a prominent language for systems programming in the future ?
Yes.

- Languages like Rust will gain more mindshare over the next decade, and be used in more and more new projects, but there are billions of lines of existing code in C, and those aren't going away.

- Hardware architects, for better or worse, largely think about software in terms of [a somewhat dated and idealized mental model of] C. So if you want to be able to converse with architects (which anyone doing systems programming should want to do), you need to have some basic fluency with C.

C also has renewed interest around IoT programming and mobile devices
I believe C will continue to be used as lingua franca after no one uses it to write software, and we're decades from even that point.

You need to know enough C to interface with the OS, and enough C to talk about memory layout, memory management, dynamic libraries, ABI, etc.

Most higher language runtimes need C, even with a self hosting compiler. Not being able to work on the C parts is limiting.

You also need to know enough assembly to be able to understand what the compiler did with your own code, even if you never write assembly yourself. Not being able to compare the disassembly to the high level language to understand why it doesn't work (or is order of magnitude slower than expected) is limiting.

What is the story behind the removal of VLAs from C99 in later revisions?
What removal? C11 section 6.7.6.2 specifies the semantics.
What the parent comment probably meant is that support for VLA was required in C99, but is no longer required in C11, so while code written for C99 could use VLAs without any special consideration, code written for C11 cannot depend on VLAs since it might not be present in all compilers.
VLAs are still present in C17 and have not been removed. They are, however, an optional feature with a truly weird (IMHO) feature testing macro. If '__STDC_NO_VLA__' is defined to 1, then the implementation does not support VLAs.

IIRC, this macro was added to C11 along with a batch of other "these are optional" macros for atomics, complex, threads, etc. However, I don't recall whether C99 adopted the features as optional features and missed the feature testing macro, or if they were required features in C99 that we made optional in C11.

Complex and VLA were required by C99, but made optional in C11. The others were new in C11.
I can't live without it.
They did not remove them, but made them optional.

Is a controverial feature, that can produce bugs, and are banned in a lot of project (one famouse, the Linux kernel).

So I spend a possibly unreasonable amount of time and page space discussing VLAs in the Effective C book. I understand there are some problems with them, but for what it is worth, I really like the feature, particularly when used in function prototype scope.
I usually don't let them leak into public interfaces, and don't allocate VLAs, but really like VLA pointers for multi-dimensional array processing such as []:

  double (*a)[N][P] = (double (*)[N][P])a_flat;
  for (i=0; i<M; i++)
    for (j=0; j<N; j++)
      for (k=0; k<P; k++)
        a[i][j][k] = f(i, j, k);
The alternative would be

        a_flat[(i*M+j)*P+k] = f(i, j, k);
which is a lot more error-prone. I understand that some implementation (MSVC) declined to implement VLAs, but I really wish that at least VLA-pointers could have remained a mandatory part of C11 and later standards.

[] Has there been any discussion of adding GCC's "typeof" to the standard?

I do find that C is difficult use for large programs. It there any thoughts that introducing features like namespaces.

Another thing very cumbersome is to do in C is object creation; creating instantiable objects is possible very cumbersome. Is there some feature in the thoughy process to deal with it. To make it clear, in C we can create a data structure like a Stack or a queue easily. But if the program needs 10 stacks then presently no simple way of achieving it.

In BRL's MUVES project, we used a 2-character prefix indicating category. E.g., all the external identifiers for our fancy memory allocator began with "Mm", where Mm.h documented the interface for the Mm package only.

To minimize the external identifiers, one could make just the name of a container structure the sole entry access handle, with structure members pointing to the functions. Then use it like:

  #include <Mm.h>
  if ((new = Mm.allo(size)) == NULL)
    Er.abort("out of memory");
Tip: you can use four leading spaces to write code.

    Like this
(comment deleted)
You only need two!

  like this
I tried, but two spaces yielded what you saw.
Huh, it also needed an extra line break before the first line of code. I didn't realize that! I've fixed it now.
You should be commended for the fast customer service!
I didn't realize that either, but it's described in formatdoc as such. So if you changed that behavior, probably should change the docs too.
I didn't change the behavior - I just added a newline. Sorry that wasn't clear.
Now that C2x plans to make two's complement the only sign representation, is there any reason why signed overflow has to continue being undefined behavior?

On a slightly more personal note: What are some undefined behaviors that you would like to turn into defined behavior, but can't change for whatever reasons that be?

Maybe someone else can respond to this as well, but I feel like the primary reason signed overflow is still undefined behavior is because so many optimizations depend upon the undefined nature of signed integer overflow. My advice has always been to use unsigned integer types when possible.

Personally, I would like to get rid of many of the trap representations (e.g., for integers) because there is no existing hardware in many cases that supports them and it gives implementers the idea that uninitialized reads are undefined behavior.

On the other hand, I just wrote a proposal to WG14 to make zero-byte reallocations undefined behavior that was unanimously accepted for C2x.

> My advice has always been to use unsigned integer types when possible.

Unsigned types have their own issues, though: they overflow at "small" values like -1, which means that doing things like correctly looping "backwards" over an array with an unsigned index is non-trivial.

> On the other hand, I just wrote a proposal to WG14 to make zero-byte reallocations undefined behavior that was unanimously accepted for C2x.

You're saying that realloc(foo, 0) will no longer free the pointer?

Some instances of undefined behavior at translation time can effectively be avoided in practice by tightening up requirements on implementations to diagnose them. But strictly speaking, because the standard allows compilers to continue to chug along even after an error and emit object code with arbitrary semantics, turning even such straightforward instances into constraint violations (i.e., diagnosable errors) doesn't prevent UB.

It might seem like defining the semantics for signed overflow would be helpful but it turns out it's not, either from a security view or for efficiency. In general, defining the behavior in cases that commonly harbor bugs is not necessarily a good way to fix them.

Just going to inject that this impacts a bunch of random optimizations and benchmarks. Just to fabricate an example:

    for (int i = 0; i < N; i += 2) {
        //
    }
Reasonably common idea but the compiler is allowed to assume the loop terminates precisely because signed overflow is undefined.

I’m not trying to argue that signed overflow is the right tool for the job here for expressing ideas like “this loop will terminate”, but making signed overflow defined behavior will impact the performance of numerics libraries that are currently written in C.

From my personal experience, having numbers wrap around is not necessarily “better” than having the behavior undefined, and I’ve had to chase down all sorts of bugs with wraparound in the past. What I’d personally like is four different ways to use integers: wrap on overflow, undefined overflow, error on overflow, and saturating arithmetic. They all have their places and it’s unfortunate that it’s not really explicit which one you are using at a given site.

The compiler assumes that the loop will alwasy terminate and that assumption is wrong, because in reality there is the possibility that the loop will not terminate, since the hardware WILL overflow.

So it's not the best solution. If we want to make this behaviour for optimizations (that are to me not worthed, giving the risk of potentially critical bugs) we must make that behavior explicit, not implicit: thus is the programmer that has to say to the compiler, I guarantee you that this operation will never overflow, if it does it's my fault.

We can agree that having a number that wraps around is not a particularly good choice. But unless we convince Intel in some way that this is bad and make the CPU trap on an overflow, so we can catch that bug, this is the behaviour that we have because is the behaviour of the hardware.

> The compiler assumes that the loop will alwasy terminate and that assumption is wrong, because in reality there is the possibility that the loop will not terminate, since the hardware WILL overflow.

The language is not a model of hardware, nor should it be. If you want to write to the hardware, the only option continues to be assembly.

> I guarantee you that this operation will never overflow, if it does it's my fault.

This is exactly what every C programmer does, all the time.

the compiler is allowed to assume the loop terminates precisely because signed overflow is undefined.

Just to be sure I understand the fine details of this -- what would the impact be if the compiler assumed (correctly) that the loop might not terminate? What optimization would that prevent?

> …what would the impact be if the compiler assumed (correctly) that the loop might not terminate?

Loaded question—the compiler is absolutely correct here. There are two viewpoints where the compiler is correct. First, from the C standard perspective, the compiler implements the standard correctly. Second, if we have a real human look at this code and interpret the programmer’s “intent”, it is most reasonable to assume that overflow does not happen (or is not intentional).

The only case which fails is where N = INT_MAX. No other case invokes undefined behavior.

Here is an example you can compile for yourself to see the different optimizations which occur:

    typedef int length;
    int sum_diff(int *arr, length n) {
        int sum = 0;
        for (length i = 0; i < n; i++) {
            sum += arr[2*i+1] - arr[2*i];
        }
        return sum;
    }
At -O2, GCC 9.2 (the compiler I happened to use for testing) will use pointer arithmetic, compiling it as something like the following:

    int sum_diff(int *arr, length n) {
        int sum = 0;
        int *ptr = arr;
        int *end = arr + n;
        while (ptr < end) {
            sum += ptr[1] - ptr[0];
            ptr += 2;
        }
        return sum;
    }
At -O3, GCC 9.2 will emit SSE instructions. You can see this yourself with Godbolt.

Now, try replacing "int" with "unsigned". Neither of these optimizations happen any more. You get neither autovectorization nor pointer arithmetic. You get the original loop, compiled in the most dumb way possible.

I wouldn’t read into the exact example here too closely. It is true that you can often figure out a way to get the optimizations back and still use unsigned types. However, it is a bit easier if you work with signed types in the first place.

Speaking as someone who does some numerics work in C, there is something of a “black art” to getting good numerics performance. One easy trick is to switch to Fortran. No joke! Fortran is actually really good at this stuff. If you are going to stick with C, you want to figure out how to communicate to the compiler some facts about your program that are obvious to you, but not obvious to the compiler. This requires a combination of understanding the compiler builtins (like __builtin_assume_aligned, or __builtin_unreachable), knowledge of aliasing (like use of the "restrict" keyword), and knowledge of undefined behavior.

If you need good performance out of some tight inner loop, the easiest way to get there is to communicate to the compiler the “obvious” facts about the state of your program and check to see if the compiler did the right thing. If the compiler did the right thing, then you’re done, and you don’t need to use vector intrinsics, rewrite your code in a less readable way, or switch to assembly.

(Sometimes the compiler can’t do the right thing, so go ahead and use intrinsics or write assembly. But the compiler is pretty good and you can get it to do the right thing most of the time.)

Thanks for the code, this is exactly the kind of concrete example I was looking for!

You're correct about how it behaves with "int" and "unsigned", very interesting. But it occurs to me that on x64 we'd probably want to use 64-bit values. If I change your typedef to either "long" or "unsigned long" that seems to give me the SSE version of the code! (in x86-64 gcc 9.3) Why should longs behave so differently from ints?

I very much agree that getting good numerics performance out of the optimizer seems to be a black art. But does the design of C really help here, or are there ways it could help more? Does changing types from signed to unsigned, or int to long, really convey your intentions as clearly as possible?

I remain skeptical that undefined behaviour is a good "hook" for compilers to use to judge programmer intention, in order to balance the risks and rewards of optimizations. (Admittedly I'm not in HPC where this stuff is presumably of utmost importance!) It all seems dangerously fragile.

If you need good performance out of some tight inner loop, the easiest way to get there is to communicate to the compiler the “obvious” facts about the state of your program and check to see if the compiler did the right thing. If the compiler did the right thing, then you’re done, and you don’t need to use vector intrinsics, rewrite your code in a less readable way, or switch to assembly.

I strongly agree with the first part of this -- communicating your intent to the compiler is key.

It's the second part that seems really risky. Just because your compiler did the right thing this time doesn't mean it will continue to do so in future, or on a different architecture, and of course who knows what a different compiler might do? And if you end up with the "wrong thing", that may not just mean slow code, but incorrect code.

> But does the design of C really help here, or are there ways it could help more?

I’m sure there are ways that it could help more. But you have to find an improvement that is also feasible as an incremental change to the language. Given the colossal inertia of the C standard, and the zillions of lines of existing C code that must continue to run, what can you do?

What I don’t want to see are tiny, incremental changes that make one small corner of your code base slightly safer. Most people don’t want to see performance regressions across their code base. That doesn’t leave a lot of room for innovation.

> It all seems dangerously fragile.

If performance is critical you run benchmarks on CI to detect regression.

> It's the second part that seems really risky.

It is safer than the alternatives, unless you write it in a different language. The “fast” code here is idiomatic, simple C the way you would write it in CS101, with maybe a couple builtins added. The alternative is intrinsics, which poses additional difficulty. Intrinsics are less portable and less safe. Less safe because their semantics are often unusual or surprising, and also less safe because code written with intrinsics is hard to read and understand (so if it has errors, they are hard to find). If you are not using intrinsics or the autovectorizer, then sorry, you are not getting vector C code today.

This is also not, strictly speaking, just an HPC concern. Ordinary phones, laptops, and workstations have processors with SIMD for good reason—because they make an impact on the real-life usability of ordinary people doing ordinary tasks on their devices.

So if we can get SIMD code by writing simple, idiomatic, and “obviously correct” C code, then let’s take advantage of that.

You shouldn't even need compiler builtins, just perform undefined behavior on a branch:

  if ((uintptr_t)ptr & alignment) {
      char *p = NULL;
      printf("%c\n", *p);
  }
I can certainly understand the value in allowing compilers to perform integer arithmetic using larger types than specified, at their leisure, or behave as though they do. Such allowance permits `x+y > y` to be replaced with `x > 0`, or `x30/15` to be replaced with `x2`, etc. and also allows for many sorts of useful loop induction.

Some additional value would be gained by allowing stores to automatic objects whose address isn't taken to maintain such extra range at their convenience, without any requirement to avoid having such extra range randomly appear and disappear. Provided that a program coerces values into range in when necessary, such semantics would often be sufficient to meet application requirements without having to prevent overflow.

What additional benefits are achieved by granting compilers unlimited freedom beyond that? I don't see any such benefits that would be worth anything near the extra cost imposed on programmers.

What should be relevant is not programmer "intent", but rather whether the behavior would likely match the that of an implementation which give that describe behavior of actions priority over parts of the Standard that would characterize them as "Undefined Behavior".
If the compiler knows that the loop will terminate in 'x' iterations, it can do things like hoist some arithmetic out of the loop. The simplest example would be if the code inside the loop contained a line like 'counter++'. Instead of executing 'x' ADD instructions, the binary can just do one 'counter += x' add at the end.
What I’m driving at is, if the loop really doesn’t terminate, it would still be safe to do that optimization because the incorrectly-optimized code would never be executed.

I guess that doesn’t necessarily help in the “+=2” case, where you probably want the optimizer to do a “result += x/2”.

In general, I’d greatly prefer to work with a compiler that detected the potential infinite loop and flagged it as an error.

Signed overflow being undefined behavior allows optimizations that wouldn't otherwise be possible

Quoting http://blog.llvm.org/2011/05/what-every-c-programmer-should-...

> This behavior enables certain classes of optimizations that are important for some code. For example, knowing that INT_MAX+1 is undefined allows optimizing "X+1 > X" to "true". Knowing the multiplication "cannot" overflow (because doing so would be undefined) allows optimizing "X*2/2" to "X". While these may seem trivial, these sorts of things are commonly exposed by inlining and macro expansion. A more important optimization that this allows is for "<=" loops like this:

> for (i = 0; i <= N; ++i) { ... }

> In this loop, the compiler can assume that the loop will iterate exactly N+1 times if "i" is undefined on overflow, which allows a broad range of loop optimizations to kick in. On the other hand, if the variable is defined to wrap around on overflow, then the compiler must assume that the loop is possibly infinite (which happens if N is INT_MAX) - which then disables these important loop optimizations. This particularly affects 64-bit platforms since so much code uses "int" as induction variables.

So in a corner case where you have a loop that iterates over all integer values (when does this ever happen?) you can optimize your loop. As a consequence, signed integer arithmetic is very difficult to write while avoiding UB, even for skilled practitioners. Do you think that's a useful trade-off, and do you think anything can be done for those of us who think it's not?
N is a variable. It might be INT_MAX so the compiler cannot optimise the loop for any value of N. Unless you make this UB.
No, it's exactly the opposite. Without UB the compiler must assume that the corner case may arise at any time. Knowing it is UB we can assert `n+1 > n`, which without UB would be true for all `n` except INT_MAX. Standardising wrap-on-overflow would mean you can now handle that corner case safely, at the cost of missed optimisations on everything else.
I hadn’t understood the utility of undefined behaviour until reading this, thank you.
I/we understand the optimization, and I'm sure you understand the problem it brings to common procedures such as DSP routines that multiply signed coefficients from e.g. video or audio bitstreams:

for (int i = 0; i < 64; i++) result[i] = inputA[i] * inputB[i];

If inputA[i] * inputB[i] overflowed, why are my credit card details at risk? The question is: can we come up with an alternate behaviour that incorporates both advantages of the i<=N optimization, as well as leave my credit card details safe if the multiplication in the inner loop overflowed? Is there a middle road?

Another problem is that there's no way to define it, because in that example the "proper" way to overflow is with saturating arithmetic, and in other cases the "proper" overflow is to wrap. Even on CPUs/DSPs that support saturating integer arithmetic in hardware, you either need to use vendor intrinsics or control the status registers yourself.
One could allow the overflow behavior to be specified, for example on the scope level. Idk, with a #pragma ? #pragma integer-overflow-saturate
I'd almost rather have a separate "ubsigned" type which has undefined behavior on overflow. By default, integers behave predictably. When people really need that extra 1% performance boost, they can use ubsigned just in the cases where it matters.
I don't know if I agree. Overflow is like uninitialized memory, it's a bug almost 100% of the time, and cases where it is tolerated or intended to occur are the exception.

I'd rather have a special type with defined behavior. That's actually what a lot of shops do anyways, and there are some niche compilers that support types with defined overflow (ADI's fractional types on their Blackfin tool chain, for example). It's just annoying to do in C, this is one of those cases where operator overloading in C++ is really beneficial.

> I don't know if I agree. Overflow is like uninitialized memory, it's a bug almost 100% of the time, and cases where it is tolerated or intended to occur are the exception.

Right, but I think the problem is that UB means literally anything can happen and be conformant to the spec. If you do an integer overflow, and as a result the program formats your hard drive, then it is acting within the C spec.

Now compiler writers don't usually format your hard drive when you trigger UB, but they often do things like remove input sanitation or other sorts of safety checks. It's one thing if as a result of overflow, the number in your variable isn't what you thought it was going to be. It's completely different if suddenly safety checks get tossed out the window.

When you handle unsanitized input in C on a security boundary, you must literally treat the compiler as a "lawful evil" accomplice to the attackers: you must assume that the compiler will follow the spec to the letter, but will look for any excuse to open up a gaping security hole. It's incredibly stressful if you know that fact, and incredibly dangerous if you don't.

> When you handle unsanitized input in C on a security boundary, you must literally treat the compiler as a "lawful evil" accomplice to the attackers: you must assume that the compiler will follow the spec to the letter, but will look for any excuse to open up a gaping security hole. It's incredibly stressful if you know that fact, and incredibly dangerous if you don't.

I'd say more chaotic evil, since the Standard has many goofy and unworkable corner cases, and no compiler tries to handle them all except, sometimes, by needlessly curtailing optimizations. Consider, for example:

    int x[2];
    int test(int *restrict a, int *b)
    {
      *a = 1;
      int *p = x+(a!=b);
      *p = 2;
      return *a;
    }
The way the Standard defines "based upon", if a and b are both equal to x, then p would be based upon a (since replacing a with a pointer to a copy of x would change the value of p). Some compilers that ignore "restrict" might generate code that accommodates the possibility that a and b might both equal x, but I doubt there are any that would generally try to optimize based on the restrict qualifier, but would hold off in this case.
Integer overflow is more than a 1% performance boost, as it lets you do a lot of things with loops.
I once did a stupid test using either a int or unsigned in a for loop variable the performance hit was about 1%. Problem is modern processors can walk, chew gum, and juggle all at the same time. Which tends to negate a lot of simplistic optimizations.

Compiler writers tend to assume the processor is a dumb machine. But modern ones aren't, they do a lot of resource allocation and optimization on the fly. And they do it in hardware in real time.

> Compiler writers tend to assume the processor is a dumb machine.

A lot of C developers tend to assume the compiler is a dumb program ;) There are significant hoisting and vectorization optimizations that signed overflow can unlock, but they can't always be applied.

If C had real array types the compiler could do real optimizations instead of petty useless ones based on UB.
Fair, hence the push in many language for range-based for loops that can optimize much better.
> modern processors can walk, chew gum, and juggle all at the same time

It's easier than it sounds. One of the major problems you usually run into when learning to juggle is that you throw the balls too far forward (their arc should be basically parallel to your shoulders, but it's easy to accidentally give them some forward momentum too), which pulls you forward to catch them. Being allowed to walk means that's OK.

(For the curious, there are three major problems you're likely to have when first learning to juggle:

1. I can throw the balls, but instead of catching them, I let them fall on the ground.

2. My balls keep colliding with one another in midair.

3. I keep throwing the balls too far forward.)

There's actually a niche hobby called "joggling" which, as the name implies, involves juggling while jogging.

Have you considered adding intrinsic functions for arithmetic operations that _do_ have defined behavior on overflow. Such as the overflowing_* functions in rust?
The semantics most programs need for overflow are to ensure that (1) overflow does not have intolerable side effects beyond yielding a likely-meaningless value, and (2) some programs may need to know whether an overflow might have produced an observably-arithmetically-incorrect result. A smart compiler for a well-designed language should in many cases be able to meet these requirements much more efficiently than it could rigidly process the aforementioned intrinsics.

A couple of easy optimizations, for example, that would be available to a smart compiler processing straightforwardly-written code to use automatic overflow checking, but not to one fed code that uses intrinsics:

1. If code computes x=yz, but then never uses the value of x, a compiler that notices that x is unused could infer that the computation could never be observed to produce an arithmetically-incorrect result, and thus there would be no need to check for overflow.

2. If code computes xy/z, and a compiler knows that y=z*2, the compiler could simplify the calculation to x+x, and would thus merely have to check for overflow in that addition. If code used intrinsics, the compiler would have to overflow check the multiplication, which on most platforms would be more expensive. If an implementation uses wrapping semantics, the cost would be even worse, since an implementation would have to perform an actual division to ensure "correct" behavior in the overflow case.

Having a language offer options for the aforementioned style of loose overflow checking would open up many avenues of optimization which would be unavailable in language that only over precise overflow checking or no overflow checking whatsoever.

oops, i meant the wrapping_* functions
If one wants a function that will compute xy/z when xy doesn't overflow, and yield some arbitrary value (but without other side-effects) when it does, wrapping functions will often be much slower than would be code that doesn't have to guarantee any particular value in case of overflow. If e.g. y is known to be 30 and z equal to 15, code using a wrapping multiply would need to be processed by multiplying the value by 30, computing a truncated the result, and dividing that by 15. If the program could use loosely-defined multiplication and division operators, however, the expression could be simplified to x+x.
No, the optimizations referred to include those that will make the program faster when N=100.
> for (i = 0; i <= N; ++i) { ... }

The worst thing is that people take it as acceptable that this loop is going to operate differently upon overflow (e.g. assume N is TYPE_MAX) depending on whether i or N are signed vs. unsigned.

Is this a real concern, beyond 'experts panel' esoteric discussion? Do folks really put a number into an int, that is sometimes going to need to be exactly TYPE_MAX but no larger?

I've gone a lifetime programming, and this kind of stuff never, ever matters one iota.

Yes, people really do care about overflow. Because it gets used in security checks, and if they don't understand the behavior then their security checks don't do what they expected.

https://gcc.gnu.org/bugzilla/show_bug.cgi?id=30475 shows someone going hyperbolic over the issue. The technical arguments favor the GCC maintainers. However I prefer the position of the person going hyperbolic.

That example was not 'overflow'. It was 'off by one'? That seems uninteresting, outside as you say the security issue where somebody might take advantage of it.
That example absolutely was overflow. The bug is, "assert(int+100 > int) optimized away".

GCC has the behavior that overflowing a signed integer gives you a negative one. But an if tests that TESTS for that is optimized away!

The reason is that overflow is undefined behavior, and therefore they are within their rights to do anything that they want. So they actually overflow in the fastest way possible, and optimize code on the assumption that overflow can't happen.

The fact that almost no programmers have a mental model of the language that reconciles these two facts is an excellent reason to say that very few programmers should write in C. Because the compiler really is out to get you.

Sure. Sorry, I was ambiguous. The earlier example of ++i in a loop I was thinking of. Anyway, yes, overflow for small ints is a real thing.
The very few times I've ever put in a check like that, I always do something like i < MAX_INT - 5 just to be sure, because I'm never confident that I intuitively understand off-by-one errors.
Same here. But I instead run a loop over a range around MAX_INT (or wherever the issue is) and print the result, so I know I'm doing what I think I'm doing. Exhaustive testing is quick, with a computer!
This isn't a good idea either: if you're dealing with undefined behavior the way the complier translates your code can change from version to version, so you could end up with a code that works with the current version of GCC but doesn't work on the next. Personally I don't agree with the way GCC and other comipers deal with UB, but this would be off topic.
Hm. May be off-topic by now. Incrementing an int is going to work the same on the same hardware, forever. Nothing the compile has any say in.
If a compiler decides that it's going to process:

    unsigned mul(unsigned short x, unsigned short y)
    { return x*y; }
in a way that causes calling code to behave in meaningless fashion if x would exceed INT_MAX/y [something gcc will sometimes actually do, by the way, with that exact function!], the hardware isn't going to have any say in that.
I've always thought that assuming such things should be wrong, because if you were writing the Asm manually, you would certainly think about it and NOT optimise unless you had a very good reason why it won't overflow. Likewise, I think that unless the compiler can prove that it, it should, like the sane human, refrain from making the assumption.
Well, by that reasoning, if you were coding in C, you would certainly think about it and ensure overflows won't happen.

The fact is that if the compiler encounters undefined behaviour, it can do basically whatever it wants and it will still be standard-compliant.

Beside optimization (as others have pointed out), disallowing wrapping of signed values has the important safety benefit that it permits run-time (and compile-time) detection of arithmetic overflow (e.g. via -fsanitize=signed-integer-overflow). If signed arithmetic were defined to wrap, you could not enable such checks without potentially breaking existing correct code.
Could we instead just have standard-defined integer types which saturate or trap on overflow?

Sometimes you're writing code where it really, really matters and you're more than willing to spend the extra cycles for every add/mul/etc. Having these new types as a portable idiom would help.

There was a proposal for a checked integer type that you might want to look at:

N2466 2020/02/09 Svoboda, Towards Integer Safety

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2466.pdf

The committee asked the proposers for further work on this effort.

Integer types that saturate are an interesting idea. Because signed integer overflow is undefined behavior, implementations are not prohibited from implementing saturation or trapping on overflow.

Eh? I thought that would only be "legal" if it was specified to be implementation-defined behavior. Which would, frankly, be perfectly good. But since it is specified as undefined behavior, programmers are forbidden to use it, and compilers assume it doesn't happen/doesn't exist.

The entire notion that "since this is undefined behavior it does not exist" is the biggest fallacy in modern compilers.

The rule is: If you want your program to conform to the C Standard, then (among other things) your program must not cause any case of undefined behavior. Thus, if you can arrange so that instances of UB will not occur, it doesn't matter that identical code under different circumstances could fail to conform. The safest thing is to make sure that UB cannot be triggered under any circumstances; that is, defensive programming.
Where does that myth come from!? According to the authors of C89 and C99, Undefined Behavior was intended to, among other things, "identify areas of conforming language extension" [their words]. Code which relies upon UB may be non-portable, but the authors of the Standard expressly did not wish to demean such code; that is why they separated out the terms "conforming" and "strictly conforming".
I don't think it's a myth so much as a misunderstanding of terminology. If an implementation defines some undefined behavior from the standard, it stops being undefined behavior at that point (for that implementation) and is no longer something you need to avoid except for portability concerns.

You're exactly right that this is why there is a distinction between conforming and strictly conforming code.

The problem is that under modern interpretation, even if some parts of the Standard and a platform's documentation would define the behavior of some action, the fact that some part of the Standard would regards an overlapping category of constructs as invoking UB overrides everything else.
I could imagine misguided readings of some coding standard advice that would lead to that interpretation, but it's still not an interpretation that makes sense to me.

Implementations define undefined behavior all the time and users rely on it. For instance, POSIX defines that you can convert an object pointer into a function pointer (for dlsym to work), or implementations often rely on offsets from a null pointer for their 'offsetof' macro implementation.

Such an interpretation would be the only way to justify the way the maintainers of clang and gcc actually behave in response to complaints about their compilers' "optimizations".
Another approach would be a standard library of arithmetic routines that signal overflow.

If people used them while parsing binary inputs that would prevent a lot of security bugs.

The fact that this question exists and is full of wrong answers suggests a language solution is needed: https://stackoverflow.com/questions/1815367/catch-and-comput...

Microsoft in particular has a simple approach to this with things like DWordMult().

    if (FAILED(DWordMult(a, b, &product)))
    {
       // handle error
    }
Clang and GCC's approach for these operations is even nicer FWIW (__builtin_[add/sub/mul]_overflow(a, b, &c)), which allow arbitrary heterogenous integer types for a, b, and c and do the right thing.

I know there's recently been some movement towards standardizing something in this direction, but I don't know what the status of that work is. Probably one of the folks doing the AUA can update.

We've been discussing a paper on this (http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2466.pdf) at recent meetings and it's been fairly well-received each time, but not adopted for C2x as of yet.
It feels like it would be a real shame to standardize something that gives up the power of the Clang/GCC heterogeneous checked operations. We added them in Clang precisely because the original homogeneous operations (__builtin_smull_overflow, etc) led to very substantial correctness bugs when users had to pick a single common type for the operation and add conversions. Standardizing homogeneous operations would be worse than not addressing the problem at all, IMO. There's a better solution, and it's already implemented in two compilers, so why wouldn't we use it?

The generic heterogeneous operations also avoid the identifier blowup. The only real argument against them that I see is that they are not easily implementable in C itself, but that's nothing new for the standard library (and should be a non-goal, in my not-a-committee-member opinion).

Obviously, I'm not privy to the committee discussions around this, so there may be good reasons for the choice, but it worries me a lot to see that document.

>the original homogeneous operations (__builtin_smull_overflow, etc) led to very substantial correctness bugs when users had to pick a single common type for the operation and add conversions.

Hi Stephen, thank you for bringing this to our attention. David Svoboda and I are now working to revise the proposal to add a supplemental proposal to support operations on heterogeneous types. We are leaning toward proposing a three-argument syntax, where the 3rd argument specifies the return type, like:

    ckd_add(a, b, T)
where a and b are integer values and T is an integer type, in addition to the two-argument form

    ckd_add(a, b)
(Or maybe the two-argument and three-argument forms should have different names, to make it easier to implement.)
Glad to hear it, looking forward to seeing what you come up with! The question becomes, once you have the heterogeneous operations, is there any reason to keep the others around (my experience is that they simply become a distraction / attractive nuisance, and we're better off without them, but there may be use cases I haven't thought of that justify their inclusion).
When David and I are done revising the proposal, we would like to send you a copy. If you would be interested in reviewing, can you please let us know how to get in touch with you? David and I can be reached at {svoboda,weklieber} @ cert.org.

>once you have the heterogeneous operations, is there any reason to keep the others around

The two-argument form is shorter, but perhaps that isn't a strong enough reason to keep it. Also, requiring a redundant 3rd argument can provide an opportunity for mistakes to happen if it gets out of sync with the type of first two arguments.

As for the non-generic functions (e.g., ckd_int_add, ckd_ulong_add, etc.), we are considering removing them in favor of having only the generic function-like macros.

You can enable this in GCC on a compilation unit basis with `-fsanitize=signed-integer-overflow`. In combination with `-fsanitize-undefined-trap-on-error`, the checks are quite cheap (on x86, usually just a `jo` to a `ud2` instruction).

(Note that while `-ftrapv` would seem equivalent, I've found it to be less reliable, particularly with compile-time checking.)

> Now that C2x plans to make two's complement the only sign representation, is there any reason why signed overflow has to continue being undefined behavior?

I presume you'd want signed overflow to have the usual 2's-complement wraparound behavior.

One problem with that is that a compiler (probably) couldn't warn about overflows that are actually errors.

For example:

    int n = INT_MAX;
    /* ... */
    n++;
With integer overflow having undefined behavior, if the compiler can determine that the value of n is INT_MAX it can warn about the overflow. If it were defined to yield INT_MIN, then the compiler would have to assume that the wraparound was what the programmer intended.

A compiler could have an option to warn about detected overflow/wraparound even if it's well defined. But really, how often do you want wraparound for signed types? In the code above, is there any sense in which INT_MIN is the "right" answer for any typical problem domain?

> In the code above, is there any sense in which INT_MIN is the "right" answer for any typical problem domain?

There is no answer different that INT_MIN that would be right and make sense, i.e. the natural properties of the + operator (associativity, commutativity) are respected. Thus, by want of another possibility, INT_MIN is precisely the right answer to your code.

I read your code and it seems to me very clear that INT_MIN is exactly what the programmer intended.

> I read your code and it seems to me very clear that INT_MIN is exactly what the programmer intended.

Well, I'm the author and that's not what I intended.

I used INT_MAX as the initial value because it was a simple example. Imagine a case where the value happens to be equal to INT_MAX, and then you add 1 to it.

The fact that no result other than INT_MIN makes sense doesn't imply that INT_MIN does make sense. Saturation (having INT_MAX + 1 yield INT_MAX) or reporting an error seem equally sensible. We don't know which behavior is "correct" without knowing anything about the problem domain and what the program is supposed to do.

A likely scenario is that the programmer didn't intend the computation to overflow at all, but the program encountered input that the programmer hadn't anticipated.

INT_MAX + 1 commonly yields INT_MIN because typical hardware happens to work that way. It's not particularly meaningful in mathematical terms.

As for "natural properties", it violates "n + 1 > n". C integers are not, and cannot be, mathematical integers (unless you can restrict values to the range they support).

Being brutal heterodox: STOP WRITING SIGNED ARITHMETIC.

Your code assumes that negating a negative value is positive. Your division check forgot about INT_MIN / -1. Your signed integer average is wrong. You confused bitshift with division. etc. etc. etc.

Unsigned arithmetic is tractable and should be treated with caution. Signed arithmetic is terrifying and should be treated with the same PPE as raw pointers or `volatile`.

This applies if arithmetic maps to CPU instructions, but not to Python or Haskell or etc. If you have automatic bignums, signed arithmetic is of course better.

Not about the language exactly, so maybe not fair game, but: how did you all find yourselves joining ISO? And maybe more generally, what's the path for someone like a regular old software engineer to come to participate in the standardization process for something as significant and ubiquitous as the C programming language?
Great question!

Joining the committee requires you to be a member of your country's national body group (in the US, that's INCITS) and attend at least some percentage of the official committee meetings, and that's about it. So membership is not difficult, but it can be expensive. Many committee members are sponsored by their employers for this reason, but there's no requirement that you represent a company.

I joined the committees because I have a personal desire to reduce the amount of time it takes developers to find the bugs in their code, and one great way to reduce that is to design features to make harder to write the bugs in the first place, or to turn unbounded undefined behavior into something more manageable. Others join because they have specific features they want to see adopted or want to lend their domain expertise in some area to the committee.

Related to that: C++ standards body seems to be quite open allowing non-members to participate (outside official votes, while respecting them when looking for consensus) is it just due to my limited observation or is the C group less open? Any plans in that regard?
Most of us on the committee would like to see more participation from other experts. The committee's mailing list should be open even to non-members. Attendance by non-members at meetings might require an informal invitation (I imagine a heads up to the convener should do it).
I think that's right. These days, much of the discussion occurs through study subgroups (like the floating-point guys) and the committee e-mailing list.
I would love to see more open interactions between the broader C community and the WG14 committee. One of the plans I am currently working on is an update to the committee's webpage to at least make it more obvious as to how you can get involved. The page isn't ready to go live yet, but will hopefully be in shape soon.
Quite a few new languages generate C code for the “backend” of their compiler. For example ATS and the ZZ language.

This helps bringing these languages to embedded targets with closed toolchains (with an existing C compiler).

Will there be developments to use a subset of C as a “portable assembly” in a standard way? Like there is WebAssembly for JavaScript.

That doesn't seem likely. There have been no proposals for anything like it and there is a general resistance to subsetting either C or C++ (the exception being making support for new features optional).
Thank you for taking time to take questions!

Have you ever considered or will you consider deprecating char, int, long, (s)size_t, float, double and etc in favour of specific length types?

Will you ever add / have you considered adding [su]\d+ and f\d+ as synonyms for those mentioned stdint.h?

Since char is signed on most platforms, arm eabi being an exception and even there it's really just a matter of compile time flags, will you ever just drop char from being able to be either and just say it's signed, as int is also signed?

Will you ever define / have you considered defining signed overflow behaviour?

I don't think we'll ever deprecate char, int, long, float, double, or size_t. ssize_t is not part of the C Standard, and hopefully never will be as it is a bit of an abomination. The main driver behind the evolution of the C Standard is not to break existing code written in C, because the world largely runs on C programs.

C does provide fixed width types like uint8_t, uint16_t, uint32_t, and uint64_t. These are optional types because they can't be implemented on implementations that don't have the appropriate word sizes. We also have required types such as

uint_least16_t uint_least32_t uint_least64_t uint_least8_t

(comment deleted)
>The main driver behind the evolution of the C Standard is not to break existing code written in C, because the world largely runs on C programs.

If not deprecate, then at least make fixed width types as equivalent members to them, ie all char based apis should accept s8 (typedef signed char s8) and all int based apis should accept s32.

Well, there are number of problems with this proposal. For example, if your implementation defines int as a 16-bit type (which is permitted for by the standard) and you pass an int32_t, the value you pass maybe truncated if it is outside of the range of the narrower type. When programming, it is best to match the type of the API of the function you are calling for portability.
Those types should not be optional. CHAR_BIT needs to be 8. It is clearly possible to implement the types even on a 6502 or Alpha. From the early days of pre-ANSI C, the language supported types for which the hardware did not have appropriate word sizes. There was a 32-bit long on the 16-bit PDP-11 hardware.

I would go beyond that, requiring all sizes that are a multiple of 8 bits from 8-bit through 512-bit. This better supports cryptographic keys and vector registers.

> CHAR_BIT needs to be 8.

Why?

Everything breaks if it isn't.

I was on an OS development team in the 1990s. We were using the SHARC DSP, which was naturally a word-addressed chip. Endianness didn't exist in hardware, since everything was whatever size (32, 40, or 48 bits) you had on the other end of the bus. Adding 1 to a hardware pointer would move by 1 bus width. The chip vendor thought that CHAR_BIT could be 32 and sizeof(long) could be 1.

We couldn't ship it that way. Customers wanted to run real-world source code and they wanted to employ normal software developers. We hacked up the compiler to rotate data addresses by 2 bits so that we could make CHAR_BIT equal to 8.

That was the 1990s, with an audience of embedded RTOS developers who were willing to put up with almost anything for performance. People are even less forgiving today. If strangely sized char couldn't be a viable product back in the 1990s, it has no chance today. It's dead. CHAR_BIT is 8 and will forever be so.

This was a really interesting and enlightening comment and a small story! Thank you!
I'd love your opinion on the abundance of "undefined behaviour" (as opposed to implementation-defined, or some new incantation such as "unknown result in variable but system is safe") for relatively trivial things such as signed (but not unsigned) integer overflows. I've heard that this is to allow for non-twos-complement implementations. However, in practice, you notice that most people use ugly workarounds which lead to ugly code that (because of e.g. casting to unsigned and allowing the same overflow to happen anyway) only work correctly on twos-complement anyway. Is this intended to be addressed in the future in some way?
> (because of e.g. casting to unsigned and allowing the same overflow to happen anyway) only work correctly on twos-complement anyway

Unsigned arithmetic never overflows, and guarantees two's-complement behavior, because unsigned arithmetic is always carried out modulo 2^n:

> A computation involving unsigned operands can never overflow, because a result that cannot be represented by the resulting unsigned integer type is reduced modulo the number that is one greater than the largest value that can be represented by the resulting type. (6.2.5, Types)

Doing the computation in unsigned always does the "right thing"; the thing that one needs to be careful of with this approach is the conversion of the final result back to the desired signed type (which is very easy to get subtly wrong).

And are there standard primitives to do this correctly (signed-unsigned-signed conversion) that never invoke undefined behavior?
Signed to unsigned conversion is fully defined (and does the two's complement thing):

> Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type (6.3.1.3 Signed and unsigned integers)

Unsigned to signed is the hard direction. If the result would be positive (i.e. in range for the signed type), then it just works, but if it would be negative, the result is implementation-defined (but note: not undefined). You can further work around this with various constructs that are ugly and verbose, but fully defined and compilers are able to optimize away. For example, `x <= INT_MAX ? (int)x : (int)(x + INT_MIN) + INT_MIN` works if int has a twos-complement representation (finally guaranteed in C2x, and already guaranteed well before then for the intN_t types), and is optimized away entirely by most compilers.

Wrapping around the modulus to me is an "overflow", although maybe the spec doesn't use the word that way
There is also a difference in x86 assembly, and probably others.

For unsigned operations the carry flag is used, and for signed operations, the overflow flag is used.

Most compilers will translate unsigned (x + y < x) to CF usage.
Right, there are (at least) two ways to describe this.

One is that unsigned arithmetic can overflow, and the behavior on overflow is defined to wrap around.

Another is to say that unsigned arithmetic cannot overflow because the result wraps around.

Both correctly describe the way it works; they just use the word "overflow" in different ways.

The C standard chooses the second way of describing it.

Interesting. I guess most/many arch's overflow flag is set when the sign bit changes and the carry flag when the result rollsover the word size.

I think most people colloquially call going A + 1 = B where B < A an overflow. Interesting. I knew they're different things, but never really thought about my word choice.

A quibble on wording: Unsigned overflow is not "twos-complement". It gives you the same bit patterns that typical two's-complement overflow gives you, but strictly speaking two's-complement is a representation for signed values.
Is there a rule that any new proposals must already be a feature in an existing major implementation?
(Not one of the OPs:) Wasn't C11 Annex K, the notoriously failed bounds-checking interfaces, a example of not having an existing implementation?
Annex K had an existing implementation from Microsoft. It wasn't a fully conforming implementation when C11 shipped, however (the specification drifted apart from the initial implementation).
Yes, the C2x charter has this requirement: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2086.htm
Thanks, so from "Only those features that have a history and are in common use by a commercial implementation should be considered", this precludes stuff that may only exist in clang, gcc, glibc, etc.? If so, why?
I wouldn't read into "commercial" there, I think we meant "production-quality" instead. (We should fix that!)

Basically, we prefer seeing features that real users have used as opposed to an experimental branch of a compiler that doesn't have usage experience. Knowing it can be implemented is one thing, but knowing users want to use it is more compelling.

You could interpret that as "in common use by a commercial[ly used] implementation".
Will you ever add / have you considered adding sane formatting options for fixed length variables in printf? Say %u32 or %s64 ?

Have you considered adding access to structure members by index or by string name? Have you considered dynamic structures?

> Will you ever add / have you considered adding sane formatting options for fixed length variables in printf? Say %u32 or %s64 ?

I'm not certain about the historical answer to this, but I do know that we're currently considering a proposal to introduce an exact bit-width integer type '_ExtInt(N)' to the language, and how to handle format specifiers for it is part of those discussions, so we are considering some changes in this area.

> Have you considered adding access to structure members by index or by string name? Have you considered dynamic structures?

I don't recall seeing any such proposals. I'm not familiar with the term "dynamic structures", what do you have in mind there?

>and how to handle format specifiers for it is part of those discussions, so we are considering some changes in this area.

Please, please, please pick short and descriptive format specifiers, like %[suf]\d+, ie

  s64 v=somenumber;
  printf("%s64\n", v);
_ExtInt(N) and PRIx64 etc look absolutely horrid. u?int\d+_t are also really bad, it would be great to have just [suf]\d+ as types, where \d+ is 8, 16, 32, 64 for [us] and 32 and 64 for f.

>what do you have in mind there?

Say like VLAs but structures with members that are dynamically defined and used.

> Please, please, please pick short and descriptive format specifiers, like %[su]\d+, ie

That's my personal preference as well. Using the PRI macros always makes me feel sad.

> Say like VLAs but structures with members that are dynamically defined and used.

Ah, no, I don't recall any proposals along those lines. It's an interesting idea, and I'd be curious what the runtime performance characteristics would be vs what kind of new coding patterns would emerge that you couldn't do previously though!

First, I agree on the PRI macros. I refuse to use them.

Stucture member access by name is useful. It's slow, but it doesn't affect code that isn't using the feature. The worst runtime issue is that the runtime support requirement grows. For example, libgcc would gain a few functions.

We can do it today with awkward code, sometimes involving hacks that are outside the C language. Implementations vary by how much they hide what is going on. When I implemented libproc.so for Linux, I made two implementations. The high-performance one used a perfect hash table that was generated by gperf and then hand-edited. Name look-up would do the hash, index into an array of structs, compare the name for a match, and then use gcc's computed goto extension to jump to code that would handle the struct member. Had I not been also parsing the data in various distinct ways, I might have used an offsetof() macro to let generic code fill in the struct fields. The other implementation I made, with lower performance, used bsearch on a sorted array.

Dynamically defined struct members are also useful, but even slower and with even more overhead. Again though, I don't think that other code would be affected beyond the growth of the compiler's libgcc equivalent.

Seeing what I just wrote above, the computed goto extension is more important. It's great for any kind of table look-up that needs code to run. Emulators use it a lot, and would use it much more if it were in the C standard.

Just FYI -- there are macros for the fixed-length types, e.g.:

    printf("U32: %" PRIu23 ", U64: " PRId64, (uint32_t)1, (int64_t)2);
Perhaps not as handy as %u32 or %s64, but it's here.
(comment deleted)
Yeah, and the issue is with those macros exactly. It makes writing code on them really damn annoying and it relies on C constant string concatenation, breaks the flow quite a lot.
Which is why I usually convert to intmax_t or uintmax_t, or to some type that I know is wide enough:

    uint64_t foo = ...;
    printf("foo = %ju\n", (uintmax_t)foo);
    /* OR */
    printf("foo = %llu\n", (unsigned long long)foo);
I think what emilfihlman means is those macros are hard to remember and clumsy to use - which you might agree with when I point out you made two mistakes in two usages :-p
What are two or three C codebases that are elegantly and cleanly written, and that every mid-level C programmer should read for sake of knowledge?
I would recommend musl, although the style is a bit idiosyncratic in places: https://www.musl-libc.org

Mbed TLS, since I have it in mind from another thread, is also a pretty clean C library for the problem it tries to solve; it's a testament to its design that we (TrustInSoft, who had not participated to its development) were able to verify that some uses of the library were free of Undefined Behavior: https://tls.mbed.org

> "I would recommend musl, although the style is a bit idiosyncratic in places: https://www.musl-libc.org"

Opened a random part of musl out of sheer boredom. Here's what I see:

https://git.musl-libc.org/cgit/musl/tree/include/aio.h

A bunch of return codes #defined like so (see https://git.musl-libc.org/cgit/musl/tree/src/aio/aio.c):

#define AIO_CANCELED 0 #define AIO_NOTCANCELED 1 #define AIO_ALLDONE 2

#define LIO_READ 0 #define LIO_WRITE 1 #define LIO_NOP 2

#define LIO_WAIT 0 #define LIO_NOWAIT 1

Why weren't they using an enum instead? I wouldn't sign off on this code (and I don't think it lives up to best practices).

musl is implementing POSIX. POSIX requires those constants to be preprocessor defines. (Generally, musl asssumes the reader is quite familiar with the C and POSIX standards, which makes sense since it's a libc implementation.)
C has been making strides towards complete Unicode support. I've been having trouble following along though: Am I correct in assuming that there's no actual multi-byte UTF-8 to UTF-32 Rune function and the best approximation depends on whatever wchar_t is? How would I best handle pure Unicode input and output scenarios on a "hostile" OS whose native character encoding is some EBCDIC abomination or a Windows codepage?
Probably link libicu rather than rely on libc.
libicu is a 40MB mess where you need only 5Kb of it. Only case folding and one normalization is needed, with tiny tables.

Additionally the used UNICODE_MAJOR and _MINOR are needed. They are always years behind, and you never know which tables versions are implemented.

  -Wl,--gc-sections
Converting arrays of utf8-encoded char to arrays of utf32-encoded 'rune' would probably not do what you want. That still leaves e.g. combining diacritical marks as separate from the characters they modify. If you care about breaking up text into codepoints, you probably also care about that sort of thing. The base unit of unicode is the extended grapheme cluster. In order to actually convert text into extended grapheme clusters, however, you need to have a database that tells you what kind of codepoint each codepoint is. Since c is standardized less frequently than unicode, any kind of unicode or utf support from the specification would quickly get out of date.
Are you looking for mbstowcs() or mbtowc() ?
wchar_t can be (a) not Unicode in any way, or (b) 16-bit, insufficient to represent a rune.