Size(_t) matters!

Sometime last week, a tweet from @nixCraft prompted the question, quite ironically, how do you get the maximum (largest positive) value for an integer?

Well, there are many wrong ways to find out. The one suggested in the tweet is hilariously inefficient, while, if it were C or C++ instead of C#, expecting definite result from undefined behavior. Yes, that i+1 wrapping at -1 is undefined behavior! (see, §5, ¶4, of ISO-14882:2011(E), C++11’s standard). But let’s see:

int nono()
{
int i=0;
while ((i+1)>0)
i++;

return i;
}

In -O0 mode, that is, with optimizations disabled, the code takes about 4 seconds to spew out the right answer (for my computer, anyway): 2147483647. The code generated is somewhat as expected:

0000000000400866 <_Z4nonov>:
400866: 55                      push   rbp
400867: 48 89 e5                mov    rbp,rsp
40086a: c7 45 fc 00 00 00 00    mov    DWORD PTR [rbp-0x4],0x0
400871: 8b 45 fc                mov    eax,DWORD PTR [rbp-0x4]
400874: 83 c0 01                add    eax,0x1
400877: 85 c0                   test   eax,eax
400879: 7e 06                   jle    400881 <_Z4nonov+0x1b>
40087b: 83 45 fc 01             add    DWORD PTR [rbp-0x4],0x1
40087f: eb f0                   jmp    400871 <_Z4nonov+0xb>
400881: 8b 45 fc                mov    eax,DWORD PTR [rbp-0x4]
400884: 5d                      pop    rbp
400885: c3                      ret

It does its ordinary stuff. Stack building preamble, sets i to zero in eax, loops, returns (implicitly) eax as result.

Now, with -O3, all optimizations:

00000000004008e0 <_Z4nonov>:
4008e0: eb fe                   jmp    4008e0 <_Z4nonov>

Bummer!

*
* *

So, what is the right answer?

Use the compiler and the language itself, its standard library if necessary. For the original code, which methinks is C#, int32.MaxValue is the right answer, always. For C or C++, we must use the limits headers. If, and only if, for some obscure reason these aren’t available, should you resort to the black arts. For example ((~0u)>>1), which only works if the integers are binary numbers (no, that’s not actually guaranteed by the standards for C and C++).

Type safety is a major issue in software development, especially when language features vary over platforms, something programming languages such as Java and C# work hard to eliminate and protect you from, but C and C++ thoroughly exploit to generate efficient code.

So, what’s the right method of getting what, exactly? Let’s refer to this table:

 what C C++ Bits per char CHAR_BITin std::numeric_limits::digitsin Max int(signed) value INT_MAXin std::numeric_limits::max()in Size of memory block size_tin size_tin File offset off_tin std::streamposin Pointers as integers intptr_t,uintptr_t,and ptrdiff_tin intptr_t,uintptr_t,and ptrdiff_tin

These are guaranteed by the standards to be the right types and values. Use them.

*
* *

Type safety and portability is one of the things I insist on when I teach programming. The reflex is always to use int, float or some other naïve type to store just about anything. File position? Never mind that the offsets are 128 bits, use a 32 bits int, then complain the code behaves weirdly when the file is over 4 gigabyte ¯\_(ツ)_/¯.