Implementation-defined behavior in C - c

Please, can you report some example for implementation-defined behavior in C?
For example, I know from standard that "An implementation-defined behavior is the propagation of the high-order bit
when a signed integer is shifted right."
Can you explain to me the significant of the example and report an example?
I understand that int i; i >> 3. But why this is implementation-defined?

The definition of implementation-defined behavior in C is when something is left for the compiler to decide, and the compiler documents which choice it made.
There are hundreds of such cases in the language. The standard contains a summary of most of them in Annex J.3, which is ~15 pages long.
The specific example int i; i >> 3 is undefined behavior since the variable isn't initialized.
The specific example int i=0; i >> 3 is implementation-defined because the standard says so. C17 6.5.7/5:
The result of E1 >> E2 is E1 right-shifted E2 bit positions. /--/ If E1 has a signed type and a negative value, the resulting value is implementation-defined.
In this particular case, it depends on whether the compiler picks an arithmetic shift or a logical shift instruction from the CPU instruction set. Meaning that the standard doesn't disfavour architectures that lack an arithmetic shift. Though in practice, the vast majority of CPUs are capable of doing arithmetic shift, even RISC ones.

It's implementation defined because the C standards committee declined to define what should happen. The reason why they did that is because different CPUs do different things in such circumstances, and the ethos of C (in the interests of fast execution) is to not to put too much abstraction between the C source code and the op codes running on the CPU.
They could have chosen to define a standard behaviour, but then on some CPUs that'd require compilers to generate a load of code to make up for the fact that the CPU's own op codes don't provide that standardised behaviour. Whilst perfectly possible, it inevitably wouldn't be very efficient.
Others will no doubt be able to point to more authoritative versions of that explanation!

Any code that relies on implementation defined behaviour is only guaranteed to work under a specific platform and/or compiler. Portable programs should try to avoid such behaviour.
According to: https://clc-wiki.net/wiki/C_language:Terms:Implementation-defined_behaviour
That also gives another example:
int *o = malloc(0 * sizeof *o);
may result in o either being NULL or a unique pointer (as specified in 7.20.3 of the C99 Standard).

Related

Get warning for left shifting a negative number

I am trying to generate a warning for undefined behavior on left shifting a negative number. According to this answer, left shift of a negative number in C is undefined.
The result of E1 << E2 is E1 left-shifted E2 bit positions; vacated bits are filled with zeros. If E1 has an unsigned type, the value of the result is E1×2E2, reduced modulo one more than the maximum value representable in the result type. If E1 has a signed type and non-negative value, and E1×2E2 is representable in the result type, then that is the resulting value; otherwise, the behaviour is undefined.
I am trying to understand why I don't get a warning with this code:
x << 3
gcc -Wall (version 9.1.0)
int main ()
{
int x= -4, y, z=5;
y = z << x;
y = x << 3;
return y;
}
On a separate note, I also don't get warned about left shifting by a negative number
z << x
In 5 << -4, both GCC 9.1.0 and Apple LLVM 10.0.1 with clang-1001.0.46.4, targeting x86-64, issue a warning message (“left shift count is negative” for GCC and “shift count is negative” for LLVM-clang). In -4 << 3, GCC does not issue a warning, but LLVM-clang does (“shifting a negative value is undefined”).
The C standard does not require a diagnostic in either of these cases, so whether a compiler does or not is a quality of implementation issue (or, possibly, that the compiler extends C by defining left shifts of negative values and therefore does not consider it an error).
When an operand is not a constant, as in z << x and x << 3, we may surmise the compiler fails to see the operand is negative, so it does not issue a warning. In general, these expressions may have defined behavior: If x is not negative and is within suitable bounds (is not larger than the width of z in the former case and is not so large that x << 3 overflows in the latter case), the behavior is defined (excepting the possibility that z << x may overflow). The C standard does not say that left shifts with types that may have negative values, i.e., signed types, are undefined, just that left shifts with negative values are undefined. Therefore, it would be an error for the compiler to issue a warning message whenever x were a signed type in an expression such as z << x or x << 3.
Of course, given int x = -4 and the lack of any change to x between that initialization and the expressions z << x and x << 3, a compiler could deduce that the shifts are undefined in this specific case and issue a warning. This is not required by the standard, and the failure of the compiler to do so is simply a matter of the quality of the implementation of the compiler.
The short explanation for the compiler not issuing a warning in such cases is because it isn't required to.
The definition of "undefined" in the standard also explicitly states "no diagnostic required". Which means the standard does not require an implementation to issue a diagnostic in such cases. The reason for that is that, technically, compilers may not be able to detect all instances of undefined behaviour within a reasonable time or (in some cases) at all. Some cases can only be detected at run time. Compilers are complicated pieces of code so - even if a human can easily recognise a problem - a compiler may not (on the other hand, compilers also detect problems that humans cannot find easily).
When no diagnostics are required, there are several things - all discretionary - that must happen before a compiler will issue a warning.
The first things that happen amount to "quality of implementation" concerns - a vendor or compiler developer elects to write code that detects particular cases and other code that issues a warning
The two steps (detecting a case, and warning about it) are separate and completely discretionary - the standard does not require a diagnostic so, even if code is written that detects a particular case, the compiler is still not required to issue a warning about it. Practically, even if problems are detected, the compiler developers may elect not to issue a warning for various reasons. Some warnings are about rare edge cases, so it is not worth the effort to emit them. Some warnings have an inherent "false positive" rate which tends to result in developers whinging in bug reports to the compiler vendor about unnecessary warnings - so the compiler is configured by default to not emit them.
The next requirement for a warning to be issued is that the user of the compiler must elect to have the warnings shown. Thanks to vehement lobbying of vendors by developers - most modern compilers are configured BY DEFAULT so they only issue a relatively small number of warnings. So developers who want as much help as possible from the compiler need to explicitly enable it. For example, by using -Wall options with gcc and clang.
The behavior of left-shifting any value by an amount smaller than the word size was specified in C89 for all integer values of the left operand, even though the specified behavior for negative left operands would often have been less than ideal on non-two's-complement complement platforms, or on error-trapping platforms in cases where iterated multiplication by two would overflow.
To avoid requiring implementations to behave in less-than-ideal fashion, the authors of C99 decided to allow implementations to process such cases however they saw fit. Rather than try to guess at all the places where implementations might have a good reason to deviate from the C89 behavior, the authors of the C99 Standard presumably expected that implementations would follow the C89 behavior, absent a good reason to otherwise, whether the Standard compelled them to do so or not.
If the authors of the Standard had intended any change to the processing of left-shift operations in cases where the C89 behavior made sense, there should be some mention of the change in the published Rationale document. There is none whatsoever. The only inference I can see from such omission is that going from "Behave in a particular fashion that happens to almost always make sense" to "Behave in that fashion if the implementer judges that it makes sense, and otherwise process the code in whatever other fashion the implementer sees fit" wasn't seen as a sufficient change as to justify comment. The fact that the authors of the Standard failed to mandate a behavior in no way implies that they didn't expect that general-purpose two's-complement implementations wouldn't continue to process such shifts as they always had, nor that programmers shouldn't be entitled to similar expectations.

Can a C compiler implement signed right shift "unreasonably"?

The title field isn't long enough to a capture a detailed question, so for the record, my actual question defines "unreasonable" in a specific way:
Is it legal1 for a C implementation have an arithmetic right
shift operator that returns different results, over time, for the
identical argument values? That is, must >> be a true function?
Imagine you wanted to write portable code using right-shift >> on signed values in C. Unfortunately, for you, the most efficient implementation of some critical part of your algorithm is fastest when signed right shifts fill the sign bit (i.e., they are arithmetic right shifts). Now since this behavior is implementation defined you are kind of screwed if you want to write portable code that takes advantage of it.
Just reading the compiler's documentation (which it is required to provide in the standard, but may or may not actually be easy to access or even exist) is great if you know all the compilers you will run on and will ever run on, but since that is often impossible, you might look for a way to do this portably.
One way I thought of is to simply test the compiler's behavior at runtime: it it appears to implement arithmetic2 right shift, use the optimized algorithm, but if not use a fallback that doesn't rely on it. Of course, just checking that say (short)0xFF00 >> 4 == 0xFFF0 isn't enough since it doesn't exclude that perhaps char or int values work differently, or even the weird case that it fills for some shift amounts or values but not for others3.
So given that a comprehensive approach would be to exhaustively check all shift values and amounts. For all 8-bit char inputs that's only 28 LHS values and 23 RHS values for a total of 211, while short (typically, but let's say int16_t if you want to be pedantic) totals only 220, which could be validated in a fraction of a second on modern hardware4. Doing all 237 32-bit values would take a few seconds on decent hardware, but still possibly reasonable. 64-bit is out for the foreseeable future however.
Let's say you did that and found that the >> behavior exactly matches the desired arithmetic shift behavior. Are you safe to rely on it according to the standard: does the standard constrain the implementation not to change its behavior at runtime? That is, does the behavior have to be expressible as a function of it's inputs, or can (short)0xFF00 >> 4 be 0xFFF0 one moment and then 0x0FF0 (or any other value) the next?
Now this question is mostly theoretical, but violating this probably isn't as crazy as it might seem, given the presence of hybrid architectures such as big.LITTLE that dynamically move processes from on CPU to another with possible small behavioral differences, or live VM migration between say chips manufactured by Intel and AMD with subtle (usually undocumented) instruction behavior differences.
1 By "legal" I mean according to the C standard, not according to the laws of your country, of course.
2 I.e., it replicates the sign bit for newly shifted in bits.
3 That would be kind of insane, but not that insane: x86 has different behavior (overflow flag bit setting) for shifts of 1 rather than other shifts, and ARM masks the shift amount in a surprising way that a simple test probably wouldn't detect.
4 At least anything better than a microcontroller. The inner validation loop is a few simple instructions, so a 1 GHz CPU could validate all ~1 million short values in ~1 ms at one instruction-per-cycle.
Let's say you did that and found that the >> behavior exactly matches the desired arithmetic shift behavior. Are you safe to rely on it according to the standard: does the standard constrain the implementation not to change its behavior at runtime? That is, does the behavior have to be expressible as a function of it's inputs, or can (short)0xFF00 >> 4 be 0xFFF0 one moment and then 0x0FF0 (or any other value) the next?
The standard does not place any requirements on the form or nature of the (required) definition of the behavior of right shifting a negative integer. In particular, it does not forbid the definition to be conditional on compile-time or run-time properties beyond the operands. Indeed, this is the case for implementation-defined behavior in general. The standard defines the term simply as
unspecified behavior where each implementation documents how the choice is made.
So, for example, an implementation might provide a macro, a global variable, or a function by which to select between arithmetic and logical shifts. Implementations might also define right-shifting a negative number to do less plausible, or even wildly implausible things.
Testing the behavior is a reasonable approach, but it gets you only a probabilistic answer. Nevertheless, in practice I think it's pretty safe to perform just a small number of tests -- maybe as few as one -- for each LHS data type of interest. It is extremely unlikely that you'll run into an implementation that implements anything other than standard arithmetic (always) or logical (always) right shift, or that you'll encounter one where the choice between arithmetic and logical shifting varies for different operands of the same (promoted) type.
The authors of the Standard made no effort to imagine and forbid every unreasonable way implementations might behave. In the days prior to the Standard, implementations almost always defined many behaviors based upon how the underlying platform worked, and the authors of the Standard almost certainly expected that most implementations would do likewise. Since the authors of the Standard didn't want to rule out the possibility that it might sometimes be useful for implementations to behave in other ways (e.g. to support code which targeted other platforms), however, they left many things pretty much wide open.
The Standard is somewhat vague as to the level of precision with which "Implementation Defined" behaviors need to be documented. I think the intention is that a person of reasonable intelligence reading the specification should be able to predict how a piece of code with Implementation-Defined behavior would behave, but I'm not sure that defining an operation as "yielding whatever happens to be in register 7" would be non-conforming.
From a practical perspective, what should matter is whether one is targeting quality implementations for platforms that have been developed within the last quarter century, or whether one is trying to force even deliberately-obtuse compilers to behave in controlled fashion. If the former, it may be worth
using a static assert to ensure the -1>>1==-1, but quality compilers for modern
hardware where that test passes are going to use arithmetic right shift
consistently. While targeting code to obtuse compilers might possibly have
some purpose, it's not generally possible to guard against all the ways that
a pathological-but-conforming compiler could sabotage one's code, and efforts
spent attempting to do so could often be more effectively spent on getting a
quality compiler.

UBSAN reports: -875 << 7 as undefined behaviour

Simple code snippet:
#define FOO 7
int bar = -875;
bar <<= FOO;
This is being reported by UBSAN as UB.
My understanding is that -875 << 7 is just -(875<<7)
and there is no overflow.
So, is there a real problem here?
Your understanding is incorrect.
Firstly you used bar <<= FOO syntax. This explicitly shifts bar and bar is negative. Left-shifting of negative values produces undefined behavior in C. There's no way bar <<= FOO can be interpreted as -(875<<7).
Secondly, concerning -875 << 7 in terms of operator priority: unary operators always have higher priority than binary ones, which means that -875 << 7 is (-875) << 7 and not -(875 << 7). And, again, left-shifting of negative values produces undefined behavior in C.
On a sign-magnitude machine, it is unclear what the effect of left-shifting a negative number should be, and it would not be unreasonable for such a machine to trap if such an operation was attempted. On such a machine, imposing any requirements on the behavior of a negative integer left-shift would likely have required compilers for such machines to generate extra code even in cases where the values to be shifted would always be positive. To avoid imposing such costs, the authors of the Standard declined to mandate any particular behavior.
Ones'-complement and two's-complement platforms would have no logical reason to trap when shifting a negative value (though whether -1<<1 should yield -2 or -3 on a ones'-complement machine would be unclear), but the authors of the Standard saw no reason to say that left-shifting of negative values has Undefined Behavior on platforms which use sign-magnitude integers, Implementation-Defined Behavior on platforms that use ones'-complement, and Standard-defined behavior on platforms which use two's-complement, since any two's-complement implementation would regard -1<<1 as yielding -2, whether or not the Standard mandated it, unless the author was deliberately obtuse.
Until probably 2005 or so, there would have been nothing even imaginably unsafe about code which will only be called upon to run on commonplace two's-complement machines using the left-shift operator on a negative value. Unfortunately, around that time an idea started to take hold which suggested that a compiler which avoids doing anything the Standard doesn't mandate can be more "efficient" than one which behaves usefully in situations not mandated by the Standard, and that such "efficiency" is desirable. I am not yet aware of compilers regarding the statement y=x<<1; as retroactively making the value of x non-negative, but I do not believe there is any reason to believe that they won't do so in future so unless or until some agency officially codifies the behavioral guarantees which mainstream microcomputer C compilers unanimously upheld for 25+ years such code cannot be considered "safe".

What replacements are available for formerly-widely-supported behaviors not defined by C standard

In the early days of C prior to standardization, implementations had a variety of ways of handling exceptional and semi-exceptional cases of various actions. Some of them would trigger traps which could cause random code execution if not configured first. Because the behavior of such traps was outside the scope of the C standard (and may in some cases be controlled by an operating system outside the control of the running program), and to avoid requiring that compilers not allow code which had been relying upon such traps to keep on doing so, the behavior of actions that could cause such traps was left entirely up to the discretion of the compiler/platform.
By the end of the 1990s, although not required to do so by the C standard, every mainstream compiler had adopted common behaviors for many of these situations; using such behaviors would allow improvements with respect to code speed, size, and readability.
Since the "obvious" ways of requesting the following operations are no longer supported, how should one go about replacing them in such a way as to not impede readability nor adversely affect code generation when using older compilers? For purposes of descriptions, assume int is 32-bit, ui is a unsigned int, si is signed int, and b is unsigned char.
Given ui and b, compute ui << b for b==0..31, or a value which may arbitrarily behave as ui << (b & 31) or zero for values 32..255. Note that if the left-hand operand is zero whenever the right-hand operand exceeds 31, both behaviors will be identical.
For code that only needs to run on a processor that yields zero when right-shifting or left-shifting by an amount from 32 to 255, compute ui << b for b==0..31 and 0 for b==32..255. While a compiler might be able to optimize out conditional logic designed to skip the shift for values 32..255 (so code would simply perform the shift that will yield the correct behavior), I don't know any way to formulate such conditional logic that would guarantee the compiler won't generate needless code for it.
As with 1 and 2, but for right shifts.
Given si and b such that b0..30 and si*(1<<b) would not overflow, compute si*(1<<b). Note that use of the multiplication operator would grossly impair performance on many older compilers, but if the purpose of the shift is to scale a signed value, casting to unsigned in cases where the operand would remain negative throughout shifting feels wrong.
Given various integer values, perform additions, subtractions, multiplications, and shifts, such fashion that if there are no overflows the results will be correct, and if there are overflows the code will either produce values whose upper bits behave in non-trapping and non-UB but otherwise indeterminate fashion or will trap in recognizable platform-defined fashion (and on platforms which don't support traps, would simply yield indeterminate value).
Given a pointer to an allocated region and some pointers to things within it, use realloc to change the allocation size and adjust the aforementioned pointers to match, while avoiding extra work in cases where realloc returns the original block. Not necessarily possible on all platforms, but 1990s mainstream platforms would all allow code to determine if realloc caused things to move, and determine the what the offset of a pointer into a dead object used to be by subtracting the former base address of that object (note that the adjustment would need to be done by computing the offset associated with each dead pointer, and then adding it the new pointer, rather than by trying to compute the "difference" between old and new pointers--something that would legitimately fail on many segmented architectures).
Do "hyper-modern" compilers provide any good replacements for the above which would not degrade at least one of code size, speed, or readability, while offering no improvements in any of the others? From what I can tell, not only could 99% of compilers throughout the 1990s do all of the above, but for each example one would have been able to write the code the same way on nearly all of them. A few compilers might have tried to optimize left-shifts and right-shifts with an unguarded jump table, but that's the only case I can think of where a 1990s compiler for a 1990s platform would have any problem with the "obvious" way of coding any of the above. If that hyper-modern compilers have ceased to support the classic forms, what do they offer as replacements?
Modern Standard C is specified in such a way that it can be guaranteed to be portable if and only if you write your code with no more expectations about the underlying hardware it will run on than are given by the C abstract machine the standard implicitly and explicitly describes.
You can still write for a specific compiler that has specific behaviour at a given optimization level for a given target CPU and architecture, but then do not expect any other compiler (modern or otherwise, or even a minor revision of the one you wrote for) to go out of its way to try to intuit your expectations if your code violates conditions where the Standard says that it is unreasonable to expect any well defined implementation agnostic behaviour.
Two general principles apply to standard C and standard C++:
Behavior with unsigned numbers is usually better defined than behavior with signed numbers.
Treat optimization as the quality-of-implementation issue that it is. This means that if you're worried about micro-optimization of a particular inner loop, you should read the assembly output of your compiler (such as with gcc -S), and if you discover that it fails to optimize well-defined behavior to appropriate machine instructions, file a defect report with the publisher of your compiler. (This doesn't work, however, when the publisher of the only practical compiler targeting a particular platform isn't very interested in optimization, such as cc65 targeting MOS 6502.)
From these principles, you can usually derive a well-defined way to achieve the same result, and then apply the trust-but-verify principle to the quality of generated code. For example, make shifting functions with well-defined behavior, and let the optimizer remove any unneeded checks that the architecture itself guarantees.
// Performs 2 for unsigned numbers. Also works for signed
// numbers due to rule for casting between signed and unsigned
// integer types.
inline uint32_t lsl32(uint32_t ui, unsigned int b) {
if (b >= 32) return 0;
return ui << b;
}
// Performs 3 for unsigned numbers.
inline uint32_t lsr32(uint32_t ui, unsigned int b) {
if (b >= 32) return 0;
return ui >> b;
}
// Performs 3 for signed numbers.
inline int32_t asr32(int32_t si, unsigned int b) {
if (si >= 0) return lsr32(si, b);
if (b >= 31) return -1;
return ~(~(uint32)si >> b);
}
For 4 and 5, cast to unsigned, do the math, and cast back to signed. This produces non-trapping well-defined behavior.

When undefined behavior can be considered well-known and accepted?

We know what undefined behavior is and we (more or less) know the reasons (performance, cross-platform compatibility) of most of them. Assuming a given platform, say Windows 32 bit, can we consider an undefined behavior as well-known and consistent across the platform? I understand there is not a general answer then I would restrict to two common UB I see pretty often in production code (in use from years).
1) Reference. Give this union:
union {
int value;
unsigned char bytes[sizeof(int)];
} test;
Initialized like this:
test.value = 0x12345678;
Then accessed with:
for (int i=0; i < sizeof(test.bytes); ++i)
printf("%d\n", test.bytes[i]);
2) Reference. Given an array of unsigned short* casting to (for example) float* and accessing it (reference, no padding between array members).
Is code relying on well-known UBs (like those) working by case (assuming compiler may change and for sure compiler version will change for sure) or even if they're UB for cross-platform code they rely on platform specific details (then it won't change if we don't change platform)? Does same reasoning apply also to unspecified behavior (when compiler documentation doesn't say anything about it)?
EDIT According to this post starting from C99 type punning is just unspecified, not undefined.
Undefined behavior means primarily a very simple thing, the behavior of the code in question is not defined so the C standard doesn't provide any clue of what can happen. Don't search more than that in it.
If the C standard doesn't define something, your platform may well do so as an extension. So if you are in such a case, you can use it on that platform. But then make sure that they document that extension and that they don't change it in the next version of your compiler.
Your examples are flawed for several reasons. As discussed in the comments, unions are made for type punning, and in particular an access to memory as any character type is always allowed. Your second example is really bad, because other than you seem to imply, this is not an acceptable cast on any platform that I know. short and float generally have different alignment properties, and using such a thing will almost certainly crash your program. Then, third, you are arguing for C on Windows, which is known for the fact that they don't follow the C standard.
First of all, any compiler implementation is free to define any behavior it likes in any situation which would, from the point of view of the standard, produce Undefined Behavior.
Secondly, code which is written for a particular compiler implementation is free to make use of any behaviors which are documented by that implementation; code which does so, however, may not be usable on other implementations.
One of the longstanding shortcomings of C is that while there are many situations where constructs which could Undefined Behavior on some implementations are handled usefully by others, only a tiny minority of such situations provide any means by which code can specify that a compiler which won't handle them a certain way should refuse compilation. Further, there are many cases in which the Standards Committee allows full-on UB even though on most implementations the "natural" consequences would be much more constrained. Consider, for example (assume int is 32 bits)
int weird(uint16_t x, int64_t y, int64_t z)
{
int r=0;
if (y > 0) return 1;
if (z < 0x80000000L) return 2;
if (x > 50000) r |= 31;
if (x*x > z) r |= 8;
if (x*x < y) r |= 16;
return r;
}
If the above code was run on a machine that simply ignores integer overflow, passing 50001,0,0x80000000L should result in the code returning 31; passing 50000,0,0x80000000L could result in it returning 0, 8, 16, or 24 depending upon how the code handles the comparison operations. The C standard, however, would allow the code to do anything whatsoever in any of those cases; because of that, some compilers might determine that none of the if statements beyond the first two could ever be true in any situation which hadn't invoked Undefined Behavior, and may thus assume that r is always zero. Note that one of the inferences would affect the behavior of a statement which precedes the Undefined Behavior.
One thing I'd really like to see would be a concept of "Implementation Constrained" behavior, which would be something of a cross between Undefined Behavior and Implementation-Defined Behavior: compilers would be required to document all possible consequences of certain constructs which under the old rules would be Undefined Behavior, but--unlike Implementation-Defined behavior--an implementation would not be required to specify one specific thing that would happen; implementations would be allowed to specify that a certain construct may have arbitrary unconstrained consequences (full UB) but would be discouraged from doing so. In the case of something like integer overflow, a reasonable compromise would be to say that the result of an expression that overflows may be a "magic" value which, if explicitly typecast, will yield an arbitrary (and "ordinary") value of the indicated type, but which may otherwise appears to have arbitrarily changing values which may or may not be representable. Compilers would be allowed to assume that the result of an operation will not be a result of overflow, but would refrain from making inferences about the operands. To use a vague analogy, the behavior would be similar to how floating-point would be if explicitly typecasting a NaN could yield any arbitrary non-NaN result.
IMHO, C would greatly benefit from combining the above concept of "implementation-constrained" behaviors with some standard predefined macros which would allow code to test whether an implementation makes any particular promises about its behavior in various situations. Additionally, it would be helpful if there were a standard means by which a section of code could request a particular "dialects" [combination of int size, implementation-constrained behaviors, etc.]. It would be possible to write a compiler for any platform which could, upon request, have promotion rules behave as though int was exactly 32 bits. For example, given code like:
uint64_t l1,l2; uint32_t w1,w2; uint16_t h1,h2;
...
l1+=(h1+h2);
l2+=(w2-w1);
A 16-bit compiler might be fastest if it performed the math on h1 and h2 using 16 bits, and a 64-bit compiler might be fastest if it added to l2 the 64-bit result of subtracting w1 from w2, but if the code was written for a 32-bit system, being able to have compilers for the other two systems generate code which would behave as it had on the 32-bit system would be more helpful than having them generate code which performed some different computation, no matter how much faster the latter code would be.
Unfortunately, there is not at present any standard means by which code can ask for such semantics [a fact which will likely limit the efficiency of 64-bit code in many cases]; the best one can do is probably to expressly document the code's environmental requirements somewhere and hope that whoever is using the code sees them.

Resources