Reputation: 8479
Hi I wrote a small test program to check how the function I wrote to convert string (an hexadecimal number) into a unsigned integer and I found that the code behave differently depending on the compiler or system I use.
I compiled the code below on:
(1) ideone C++4.3.2 https://ideone.com/LlcNWw
(2) g++ 4.4.7 on a centos6 (64bits)
(3) g++ 4.6.3 on an ubuntu12 (64bits)
(4) g++ 4.9.3 in a cygwin (32bits) environment
As expected (1) and (4) return AND IT'S exactly the correct result as the 1st value '0x210000000' is to big for a 32bit value....
Error while converting Id (0x210000000).
success
but (2) and (3) return
success
success
SO THE QUESTION is why the same simple C code build on different platform with different compiler return the same result... and Why 'strtoul("0x210000000", ....)' doesn't set 'errno' to 'ERANGE' to said that the bit 33 to 37 are out of range.
more trace on a the platform (3) give:
Id (0x210000000) as ul = 0x10000000 - str_end - errno 0.
sucess
Id (0x10000000) as ul = 0x10000000 - str_end - errno 0.
sucess
/* strtoul example */
#include <stdio.h> /* printf, NULL */
#include <stdlib.h> /* strtoul */
#include <errno.h>
signed int GetIdentifier(const char* idString)
{
char *str_end;
int id = -1;
errno = 0;
id = strtoul(idString, &str_end, 16);
if ( *str_end != '\0' || (errno == ERANGE))
{
printf("Error while converting Id (%s).\n", idString);
return -1;
}
// Return error if converted Id is more than 29-bit
if(id > 0x1FFFFFFF)
{
printf("Error: Id (%s) should fit on 29 bits (maximum value: 0x1FFFFFFF).\n", idString);
return -1;
}
printf("sucess\n");
return id;
}
int main ()
{
GetIdentifier("0x210000000");
GetIdentifier("0x10000000");
return 0;
}
Upvotes: 0
Views: 2149
Reputation: 409266
The value 0x210000000
is larger than 32 bits, and on 32 bit systems long
is usually 32 bits which means you can't use strtoul
to convert the string correctly. You need to use strtoull
and use unsigned long long
which is guaranteed to be at least 64 bits.
Of course, long long
and strtoull
was introduced in C99, so you might need to add e.g. -std=c99
(or use a later standard like C11) to have it build correctly.
The problem, it seems, is that you assume that long
is always 32 bits, when in fact it's defined to be at least 32 bits. See e.g. this reference for the minimum bit-size of the standard integer types.
On some platforms and compilers, long
can be bigger than 32 bits. Linux on 64-bit hardware is a typical such platform where long
is bigger, namely 64 bits, which is of course well enough to fit 0x210000000
, which leads to strtoul
not giving an error.
Upvotes: 8
Reputation: 1
Your code is also incorrect in assuming a successful call will not change the value of errno
. Per the Linux errno
man page:
The
<errno.h>
header file defines the integer variableerrno
, which is set by system calls and some library functions in the event of an error to indicate what went wrong. Its value is significant only the return value of the call indicated an error (i.e., -1 from most system calls; -1 or NULL from most library functions); a function that succeeds is allowed to changeerrno
.
(POSIX does place greater restrictions on errno
modification by successful calls, but Linux doesn't strictly adhere to POSIX in many cases, and after all, GNU's Not Unix...)
The
strtoul()
function returns either the result of the conversion or, if there was a leading minus sign, the negation of the result of the conversion represented as an unsigned value, unless the original (nonnegated) value would overflow; in the latter case, strtoul() returnsULONG_MAX
and sets errno toERANGE
. Precisely the same holds for strtoull() (withULLONG_MAX
instead ofULONG_MAX
).
Unless strtoul
returned ULONG_MAX
, the value of errno
after a call to strtoul
is indeterminate.
Upvotes: 1