Reducing undefined behavior in the C language
| Welcome to LWN.net The following subscription-only content has been made available to you by an LWN subscriber. Thousands of subscribers depend on LWN for the best news from the Linux and free software communities. If you enjoy this article, please consider subscribing to LWN. Thank you for visiting LWN.net! |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
By Jonathan Corbet September 28, 2026
---
As a professor of biomedical engineering, Martin Uecker perhaps does not fit the profile of a typical presenter at Kernel Recipes. He is, however, a longtime Linux user, and works on free software for controlling magnetic resonance imaging (MRI) scanners. He was at the conference to talk about the C programming language, the specific problem of undefined behavior in C, and whether it can eventually be made into a memory-safe language.
Why bother with C in 2026? It is, he said, still a great language. C is portable, stable over the long term, offers fast compilation, and the resulting binary code is fast. "What you see is what you get"; it is easy to look at C code and have some idea of what the computer will actually do. There are a lot of tools for working with the language, and C gets out of the way when necessary.
C does have a long history, and that affects the language as we see it today, he said. The C89 standard had to cope with a wide variety of hardware, including machines with signed-magnitude or one's-complement integer representations, segmented memory, exotic pointer representations, and surprising sizes for types. Some Honeywell machines, for example, had nine-bit bytes. That greatly complicated the task of writing a standard that would enable the writing of portable code.
[![[Martin Uecker]](https://static.lwn.net/images/conf/2026/kr/MartinUecker-sm.png "Martin Uecker")](https://lwn.net/Articles/1096226/)The approach that was taken was to define the semantics of the language in terms of an abstract machine. All operations are to be executed as if they had run on that abstract machine, which may not exactly match the actual hardware. The observable behavior of the program must be what the abstract machine would have done. The "observable" part matters: access to volatile variables, being defined as observable, must happen exactly according to the abstract machine; everything else just has to produce the same eventual result.
The standard gives a lot of freedom to compiler implementers; only the observable behavior has to be preserved. There are many aspects of that behavior that are either undefined or implementation-defined. These are not observable behavior, and thus do not constrain what compiler implementers can do. There are, of course, other specifications that can constrain compiler developers where the C standard does not; these include ABI requirements, standards like POSIX, or the need for backward compatibility.
Undefined behavior comes about when a program does something that is either not portable or not defined by the standard at all. In such cases, the C89 standard states that it "imposes no requirements" on the implementation. Undefined behavior exists for a number of reasons. It allows implementations to support extensions, manage interactions with hardware-based safety mechanisms, and perform aggressive optimization, all while allowing difficult-to-detect errors to be ignored. It explicitly gives the compiler the right to ignore whole classes of hard-to-detect errors.
#### Nasal demons
The problem, Uecker said, is that the standard allows a compiler to do anything in response to undefined behavior, up to the point of invoking nasal demons. If a program contains any undefined behavior at all, according to compiler writers, then it has no expected semantics. The C++23 standard goes further to explicitly state that the standard imposes no requirements for these programs. That has led to widespread disagreements between developers about what can be expected from the language.
For example, if you zero an entire structure (perhaps with a call to memset()), then write to specific fields, what will happen if you read from any padding bytes in that structure? Might they contain security-relevant data? A 2015 survey showed that there was no consensus on what should happen in that case. Or consider this simple code:
extern int x;
int f(int a, int b) { x = b ? 42 : 43; return a/b; }
If b is zero, then the return statement is a division by zero, which is undefined behavior. In this case, is the compiler entitled to omit the test entirely and just execute x = 42? After all, the b = 0 case has no expected semantics, and can thus be ignored. There are compilers that will do exactly that. In the undefined-behavior case, the store to x is not observable behavior. But now consider this case:
extern void g(int x);
int f(int a, int b) { g(b ? 42 : 43); return a/b; }
This might seem to be the same situation, with the compiler being entitled to remove the test and just pass 42 to g(), and some compilers have treated that way — but that compiler behavior was a bug. Imagine a definition of g() that calls exit() if b is zero. In that case, the division will never happen and the program's behavior is not undefined. So eliding the test and simply passing 42 to g() is incorrect.
One more interesting case:
volatile int x;
int foo(int a, int b, bool store_to_x) { if (! store_to_x) return a/b; x = b; return a/b; }
The question here is: can the compiler hoist the final division operation above assignment to x? If there are no semantics associated with the b = 0 case, then there is no change in observable behavior. This, too, is something compilers have done, but the C23 standard added a "no time travel" stipulation to disallow it. In C++, instead, hoisting must be explicitly prevented by inserting a call to std::observable\_checkpoint().
Time-travel bugs should eventually go away, but there are a lot of other situations where, even if the standard is clear, compiler writers often disagree. These include reading of uninitialized variables (which is almost always defined), and equality comparisons of pointers, which is always defined, but is also miscompiled by both Clang and GCC.
#### Fighting undefined behavior
To try to address all of these problems and more, the C committee runs three study groups focused specifically on the memory object model, memory safety, and undefined behavior. There are currently about 100 instances of undefined behavior in the C standard, but the in-progress C2y draft has removed 45 of them. The situation is indeed getting better.
There is an increasingly rich set of tools aimed at finding issues: compiler warnings, static analyzers, sanitizers, LLM-based tools, formal verification, and more. The number of situations where a compiler will emit a warning where possible undefined behavior is detected is growing; recent examples include better warnings for integer overflows and potential use-after-free situations. Static analyzers are available as standalone tools, but are also increasingly being built into the compilers themselves; GCC can now warn about a number of potential buffer-overflow situations, for example. Sanitizers work by inserting run-time checks; they can catch a lot of undefined behavior and, in trapping mode, be used for hardening as well.
Memory safety has never been one of C's strong points, but Uecker wanted to make the point that it can be improved. That problem breaks down into three sub-problems: type safety, spatial memory safety, and temporal memory safety.
C, he said, has a strong type system, and the remaining problems are fixable. Tagless unions, for example, can create type confusion, but the compiler can enforce types with some additional annotations. New diagnostics can catch unsafe casts from void. Type checking across translation units is traditionally not a huge problem in C, since header files are used to ensure consistent types, but the situation could be improved with a link-time checker.
Spatial memory safety — bounds checking — is a partially solved problem; the compilers can perform array-bounds checking in many situations now. In some cases, some code changes are needed to fully benefit from this checking. Use of the counted\_by attribute can enable checking for flexible array members, for example.
Temporal memory safety — avoiding use-after-free bugs and the like — is harder, Uecker said, and Rust definitely has an advantage there. Still, better temporal memory-safety enforcement is possible. Architectures like CHERIcan help here is well. Fil-C can find a lot of temporal-safety bugs.
Can all of these tools and language changes get us to full memory safety? Completely solving the problem will require either expensive run-time checking or formal verification, he said. In the near future, the most complete results will be had with the combination of a restricted language and formal verification tools.
Overall, he concluded, C is still a living language and is still improving. The C23 standard removed a number of problematic features, including old-style (K&R) function definitions, support for sign-magnitude and one's-complement machines, and trigraphs. It added bit-precise integer types, checked integer operations, and more. C2y will go further, adding case ranges, named for loops, the \_Countof() macro to determine array lengths, and a lot of "demon removal". It will not achieve full memory safety for C, but that is an eventual possibility, and will become more practical over time. He ended by encouraging interested people to participate in the working groups.
The raw video and slides from this talk are available.
\[Thanks to the Linux Foundation, LWN's travel sponsor, for supporting my travel for this event.\]
| Index entries for this article | | | ------------------------------------------------------- | -------------------------------------------------------------------------------------- | | Conference | Kernel Recipes/2026 |
| | | |
---
to post comments
CPU-dependent behavior
Posted Sep 28, 2026 17:27 UTC (Mon) by ballombe (subscriber, #9523) \[Link\] (14 responses)
A major issue is CPU-dependent behavior, like 1UL>>64. Any attend to make the result well-defined will lead to a major performance regression on half of the CPU. What does rust do ?
CPU-dependent behavior
Posted Sep 28, 2026 17:34 UTC (Mon) by daroc (editor, #160859) \[Link\] (11 responses)
I'm not aware of any CPU where 1UL>>64 is anything other than 0. Did you perhaps mean 1UL<<64?
The answer for Rust is that it depends on your compiler settings. By default, in debug builds it results in a panic which prints a stack-trace and exits. In release builds, it is guaranteed to wrap, which means it will evaluate to 0. But if you want to be certain, there are helper methods that will return an error explicitly if overflow would occur, for your program to handle however it likes.
CPU-dependent behavior
Posted Sep 28, 2026 18:35 UTC (Mon) by ballombe (subscriber, #9523) \[Link\] (8 responses)
On intel CPU, at the ASM level, the result is 1, not 0.
long fun(long a, long b) { return a>>b; } int main(void) { printf("%ld\\n",fun(1,64)); }
print 1
CPU-dependent behavior
Posted Sep 28, 2026 18:48 UTC (Mon) by daroc (editor, #160859) \[Link\] (7 responses)
... huh, so it does. I guess I was misremembering. I wonder why they implemented it like that.
CPU-dependent behavior
Posted Sep 28, 2026 18:59 UTC (Mon) by ballombe (subscriber, #9523) \[Link\]
(apparently aarch64 does the same). Probably the reason is that the CPU only needs to look at the 6 first bits of the shift.
CPU-dependent behavior
Posted Sep 28, 2026 19:24 UTC (Mon) by kreijack (guest, #43513) \[Link\] (5 responses)
\> ... huh, so it does. I guess I was misremembering. I wonder why they implemented it like that.
For the SAL/SAR/SHL/SHR intel instructions \[\*\], the counter register of the shift is masked with & 63 (or & 31 depending by the register width). So shifting by 64, is effectively shifting by 0: 1>>64 -> 1 >> (64 & 63) -> 1>>0 -> 1
The funny thing, is that testing this code in godbold, I got a lot of different results (0, 1, 0x7f49a68055c0 (!!!!!) ) depending by the combination of compiler/optimization. So, yes this is an undefined behavior, and we should expect any (un)reasonable result.
\[\*\] <https://www.felixcloutier.com/x86/sal:sar:shl:shr> /... The count operand can be an immediate value or the CL register. The count is masked to 5 bits (or 6 bits with a 64-bit operand). The count range is limited to 0 to 31 (or 63 with a 64-bit operand). A special opcode encoding is provided for a count of 1..../
CPU-dependent behavior
Posted Sep 28, 2026 19:35 UTC (Mon) by ballombe (subscriber, #9523) \[Link\] (4 responses)
And how rust deal with it without sacrificing performance ?
CPU-dependent behavior
Posted Sep 28, 2026 19:43 UTC (Mon) by mb (subscriber, #50428) \[Link\] (3 responses)
It panics in debug mode and does whatever the processor does in release mode.
Debug:
playground::a:
subq $40, %rsp
movq %rdi, 8(%rsp)
movq %rsi, 16(%rsp)
movq %rdi, 24(%rsp)
movq %rsi, 32(%rsp)
cmpq $64, %rsi
jae .LBB7_2
movq 8(%rsp), %rax
movq 16(%rsp), %rcx
andq $63, %rcx
shrq %cl, %rax
addq $40, %rsp
retq.LBB7_2:
leaq .Lanon.77e4319a821b47bb0a9368ddeddb101a.2(%rip), %rdi
callq *core::panicking::panic_const::panic_const_shr_overflow@GOTPCREL(%rip)
Release:
playground::a:
movq %rsi, %rcx
movq %rdi, %rax
shrq %cl, %rax
retqCPU-dependent behavior
Posted Sep 29, 2026 0:33 UTC (Tue) by walters (subscriber, #7396) \[Link\]
\> It panics in debug mode and does whatever the processor does in release mode.
While you probably know this, it's important to emphasize for the wider audience that Rust has almost equally ergonomic "checked" variants of arithmetic functions, in this case https://doc.rust-lang.org/stable/std/primitive.u64.html#m... that apply regardless of "debug" vs "release" builds - and it's often a good idea to use them.
In fact, for most use cases they should probably be thought of as the default.
CPU-dependent behavior
Posted Sep 29, 2026 8:36 UTC (Tue) by ralfj (subscriber, #172874) \[Link\] (1 responses)
It panics in debug mode and does whatever the processor does in release mode.
Not quite. In release mode is always shifts by "offset & (bit\_size - 1)". (To be even more pedantic, this is tied to `-Cdebug-assertions`, which is usually only set for debug builds but can be turned on in release builds as well.)
The behavior of safe integer operations in Rust is fully deterministic and portable (modulo endianess). We do pay a small performance cost for that on some targets but we consider that worth it. Operations like `unchecked_shl` are available if you are in a hot loop and want to avoid the overhead of masking with `bit_size - 1`.
CPU-dependent behavior
Posted Sep 29, 2026 9:12 UTC (Tue) by ojeda (subscriber, #143370) \[Link\]
`-Cdebug-assertions`
More specifically, `-Coverflow-checks`, which I prefer because it is not uncommon to want to enable the overflow checks while not adding all the debug assertions, e.g. in Linux we enable the overflow checks by default at the moment but not the debug assertions (though it is possible we may need to switch that, which is why I requested `-Coverflow-checks=report`).
CPU-dependent behavior
Posted Sep 28, 2026 20:00 UTC (Mon) by ojeda (subscriber, #143370) \[Link\]
Importantly, the key is that one should never rely on the wrapping nor the panicking.
In other words, if one actually overflows, then it was not intentional, it is a bug, and one is supposed to fix the code.
If one actually wants the panicking or the wrapping, then one should be explicit about it, rather than rely on that behavior.
That way, the source code is not ambiguous, and thus can be analyzed well, while the program gets to keep perfectly well-defined semantics. No need to keep or add UB to a language just for that, as has sometimes been argued.
So it is different than "normal" defined behavior. It is what nowadays one may call EB (Erroneous Behavior). I proposed naming this category in Rust back when Rust for Linux started and advocated it in C/C++/kernel discussions too, because I liked the idea from the integer operators in Rust. I presented it with that name in Kangrejos 2021, for instance. C++26 later adopted the concept under the same name, which is great.
In Linux, on the Rust side, we use EB in certain places, e.g. we may document that a function is not supposed to be called in a certain way, but if it does get called badly, rather than to panic or to get the state corrupted, it will act in a defined way (possibly including reporting the error in the kernel log and returning some sort of fixed value).
So one can see it as error handling for unwanted inputs, with the expectation that the callers should get fixed.
CPU-dependent behavior
Posted Sep 29, 2026 8:40 UTC (Tue) by ralfj (subscriber, #172874) \[Link\]
The answer for Rust is that it depends on your compiler settings. By default, in debug builds it results in a panic which prints a stack-trace and exits. In release builds, it is guaranteed to wrap, which means it will evaluate to 0.
No, it does not evaluate to 0. Did you actually try this before making your claims?
https://play.rust-lang.org/?version=stable&mode=release&edition=2024&gist=34856eaababf1c620ea0bfb7f54b480c
CPU-dependent behavior
Posted Sep 28, 2026 20:52 UTC (Mon) by azumanga (subscriber, #90158) \[Link\]
C can already allow implementation defined behaviour, this should fall into that category.
I agree these things should be left cpu-specific, just not ‘oh you did this so now UB applies and I’m going to smash your whole program’.
CPU-dependent behavior
Posted Sep 29, 2026 4:00 UTC (Tue) by Aissen (subscriber, #59976) \[Link\]
\> What does rust do ?
In Rust this is an overflow, and will panic like other int overflows if you build with integer overflow checking enabled (enabled by default in debug mode, and I'd argue most people should enable them in release mode).
https://doc.rust-lang.org/reference/expressions/operator-...
Additionally, Rust provides non-panicking variants for all integer types, with strictly defined semantics: https://doc.rust-lang.org/std/primitive.u64.html#method.w... https://doc.rust-lang.org/std/primitive.u64.html#method.c... https://doc.rust-lang.org/std/primitive.u64.html#method.u... https://doc.rust-lang.org/std/primitive.u64.html#method.s...
or unsafe functions that do not panic: https://doc.rust-lang.org/std/primitive.u64.html#method.u...
Division by zero, and other UB
Posted Sep 28, 2026 17:33 UTC (Mon) by rrolls (subscriber, #151126) \[Link\] (7 responses)
One could easily resolve the UB of "division by zero" by defining a/0 to output 0, regardless of the value of a.
Pony does this, and I think provides a good justification: <https://tutorial.ponylang.io/gotchas/divide-by-zero.html>
It does not matter that this definition is mathematically questionable. Defining a/0 to 0 is useful in \some\ circumstances (such as displaying an average, where one might output "N/A" if there are no samples, but outputting "0" is a very common choice), and the property of "all division results being defined" is useful in basically all circumstances (because it means you no longer need to worry about accidentally triggering UB!).
The "downside" to defining the result as 0 is that suddenly compilers must add extra code, which means extra cycles consumed at runtime, to check if b is 0 prior to performing the division.
However, this isn't actually a downside! People already have to write their own checks all over the place prior to performing a division, so a lot of code will already have those checks there. That means that the compiler would not emit any extra code, as it can see that by the time the division is reached, the check has already been done and b can't possibly be zero. It actually provides a benefit, because now compilers can also omit the check if the compiler can prove by some other means that b can't be zero.
This technique can resolve other types of UB, too. Reading from an uninitialised variable or an out-of-bounds pointer could be defined to return 0. Calling a function pointer that is NULL could be defined to do nothing and return 0 (or the equivalent "zero value" for the return type). Writing out of bounds could be defined to do nothing. Similarly to the div/0 case above, compilers would suddenly need to produce extra code to check for these cases and thus slow down execution by default - but precisely because compilers would need to do that, they would \know\ when they are doing that, so can also emit a warning or error depending on your compiler options, to tell you that you're invoking these extra checks. If you know that a check isn't needed (for example if you know a pointer cannot be out-of-bounds), you can add an assert, and bam, the compiler now knows it can get rid of the check; your assert will perform the check if asserts are enabled, or you will get back your "undefined behavior" if asserts are disabled for performance.
And if in your use case you don't need the performance, you could just not enable those warnings and let it silently add all the checks it wants, and you'll have guaranteed safe (even if slow) code. The obvious concern of "but zeroes will appear unexpectedly!" can then be addressed by getting into a habit of always assigning zero the meaning of "not special; default; disabled; resource not available; operation not known to be successful" or similar, and then any unexpected zero that shows up will just neatly send your program down its fallback or error path rather than doing anything dangerous.
Division by zero, and other UB
Posted Sep 28, 2026 18:41 UTC (Mon) by ballombe (subscriber, #9523) \[Link\] (5 responses)
Integer division by 0 should trigger SIGFPE like with double.
Division by zero, and other UB
Posted Sep 28, 2026 19:12 UTC (Mon) by ballombe (subscriber, #9523) \[Link\]
Apparently x86\_64 triggers a SIGFPE but aarch64 returns 0.
35 years ago the C standard committee should have used its weight to encourage CPU with standardized behaviour, but instead they tried to beat fortran at its own game and completely failed.
Division by zero, and other UB
Posted Sep 28, 2026 19:15 UTC (Mon) by Cyberax (✭ supporter ✭, #52523) \[Link\] (3 responses)
I think this is optional? Floating-point operations usually signal error conditions inline, using NaN values.
Division by zero, and other UB
Posted Sep 28, 2026 20:19 UTC (Mon) by jengelh (subscriber, #33263) \[Link\] (1 responses)
\>Floating-point operations usually signal error conditions inline, using NaN values
If only. Thanks to <fenv.h>, it's not guaranteed.
Division by zero, and other UB
Posted Sep 28, 2026 21:30 UTC (Mon) by NYKevin (subscriber, #129325) \[Link\]
Unfortunately, we cannot even blame C for this misfeature. <fenv.h> is just a portable interface to hardware-level behavior that has been burned into practically every microprocessor or FPU manufactured in the last N decades (for some value of N which people may debate in the replies, if they have nothing better to do). This also means that non-C programming languages either have to put up with it, or else grow a runtime that explicitly reconfigures it to some sensible default behavior (and then you have to deal with all the usual FFI etc. bugaboos implied by such a runtime).
Division by zero, and other UB
Posted Sep 29, 2026 8:52 UTC (Tue) by malmedal (subscriber, #56172) \[Link\]
IEE754 mandates that it be configurable. You can chose either a trap or inline signalling. If you don't the defaults vary.
Division by zero, and other UB
Posted Sep 29, 2026 8:32 UTC (Tue) by alx.manpages (subscriber, #145117) \[Link\]
Having defined behavior would benefit few use cases; and it would give erroneous answers that will hide bugs. It's preferable to entirely disallow division by zero when the operands are integer constant expressions, and UB at run-time, which analyzers can catch.
Good news
Posted Sep 29, 2026 1:56 UTC (Tue) by marcH (subscriber, #57642) \[Link\]
\> Overall, he concluded, C is still a living language and is still improving. The C23 standard removed a number of problematic features, ...
It's easy to debate what programming languages everyone should use. Whereas this sort of work is barely visible and much more important - thank you! I don't want to choose between Rust and a safer C/C++: everyone should want \both\. They are not mutually exclusive and the more "security competitions", the merrier. Computers have been insecure and crashing for way too long and everything helps. Society will depend on C and C++ for at least a couple more generations.
Doubtful
Posted Sep 29, 2026 7:47 UTC (Tue) by taladar (subscriber, #68407) \[Link\] (1 responses)
I have my doubts with all of these efforts to fundamentally move existing languages very far away from what they are today and have been for most of their life.
The main benefit from using an older language like C (if there is any) is that the name identifies a certain language behaviour that existing C programmers know and existing code bases expect.
So while it is of course possible to change all kinds of things and still call the result C it is questionable at best to do so if it costs you the compatibility with the existing code bases and the existing programmer's knowledge.
At the same time though, it is orders of magnitude harder to ever get anywhere close to the benefits of a modern language like Rust that had the benefit of being able to start with a clean slate without having to consider backwards compatibility with existing code bases.
Even if you could add enough features to the language that new code had most of the benefits, you would still have to live with having old code in your process (unless you throw away backwards compatibility completely in which case, why not call it something else) and that comes with a myriad of issues when enforcing any sort of safety or security guarantee.
Doubtful
Posted Sep 29, 2026 8:41 UTC (Tue) by alx.manpages (subscriber, #145117) \[Link\]
\> I have my doubts with all of these efforts to fundamentally move existing languages very far away from what they are today and have been for most of their life.
I don't think it changes fundamentally. It's still the same thing, just changing some UB for compiler errors, and some other UB for defined behavior (the former is preferable).
\> \> The main benefit from using an older language like C (if there is any) is that the name identifies a certain language behaviour that existing C programmers know and existing code bases expect.
Old code that had defined behavior still works, and remains having the same meaning. The worst code using deep UB, it will probably now result in compiler errors; that's fine, since it never really worked. It can be fixed (and it can also be compiled under \`-std=c89\` if UB is indeed wanted).
\> At the same time though, it is orders of magnitude harder to ever get anywhere close to the benefits of a modern language like Rust that had the benefit of being able to start with a clean slate without having to consider backwards compatibility with existing code bases.
According to Ojeda in Kernel Recipes, Rust already has applied breaking changes that silently change the behavior of some code that was valid and remains valid. That's something C has never done (IIRC) until very recently, where \`auto\` was changed to mean \`\_\_auto\_type\`. And it could be done with \`auto\` because no-one had seriously used it (there may be a few exceptions, but \`auto\` as automatic-storage duration was essentially unused).
So, even with many decades of advantage, Rust has had to do these things it didn't need to.
The meaning of "undefined behaviour."
Posted Sep 29, 2026 8:44 UTC (Tue) by rweikusat2 (subscriber, #117920) \[Link\]
Undefined behaviour is behaviour for which the C standard defines no requirements. And that's really all. For instance, to use an intentionally absurd example, the C standard defines no requirements for preseential elections in the USA and consequently, insofar the C standard is concerned, they're an example of undefined behaviour.
On a more practical note, a program has undefined behaviour the moment it calls any function not defined by the C standard. For instance, any POSIX function that's not part of it. But that's not an issue because while the C standard doesn't define any behaviour for such a case, something else, like POSIX, very well might.
Undefined by the C standard means neither wrong nor bizarre, just not defined by this particular document.
n3957 - Ghosts and Demons: Undefined Behavior in C2Y (Status 2026-08-23)
Posted Sep 29, 2026 8:48 UTC (Tue) by alx.manpages (subscriber, #145117) \[Link\]
Martin has posted a document with the status of UB in C2y, as a paper in the C Committee. Here's a link:
<<https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3957.pdf>\>
There's a mistake in UB 2, which was defined recently, but the definition is bogus, and the committee is discussing what to do with it. Hopefully, we'll transform that into a compiler error. Others are pushing to define it differently, although that's wrong (I have a draft of a committee paper for that).


