Reputation: 154230
This is an It’s OK to Ask and Answer Your Own Questions. I researched the issue, found the result curious, and posted my findings.
What value should "very negative" strings return from strtoul()
: 1
, ULONG_MAX
or what?
strtol()
For strings representing numeric values like "123"
, strtol()
behaves as expected. Strings (blue) [LONG_MIN ... LONG_MAX]
convert to their expected long
value. Strings (yellow) above LONG_MAX
convert to LONG_MAX
and set errno==ERANGE
. Strings below LONG_MIN
convert to LONG_MIN
and set errno==ERANGE
.
strtoul()
positive
For strings representing numeric values like "123"
, strtoul()
also behaves as expected. Strings (red) [0 ... ULONG_MAX]
convert to their expected unsigned long
value. Strings (green) above ULONG_MAX
convert to ULONG_MAX
and set errno==ERANGE
.
If the subject sequence begins with a minus sign, the value resulting from the conversion is negated (in the return type). C17dr § 7.22.1.4 5
strtoul()
negative
For strings (red) [-ULONG_MAX ... -1]
, the conversion negates the positive conversion and adds ULONG_MAX + 1
(like the typical assignment of a negative value to an unsigned) and does not set errno
. This is a bit surprising, but that is how the specs defines it.
strtoul()
very negative - the problem
For strings (green) less than-ULONG_MAX
, I'd expect the conversion to be handled like smaller negative values described above: the conversion negates the positive conversion (ULONG_MAX
due to overflow) and adds ULONG_MAX + 1
. Expected result 1
(or maybe 0
) with errno == ERANGE
. Yet exercising strtoul()
resulted in ULONG_MAX
.
What is correct?
Test code
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
long strtol_test(const char *s, int base) {
printf("\n");
int width = snprintf(NULL, 0, "%ld", LONG_MIN);
printf("base:%2d \"%s\"\n", base, s);
char *endptr_signed;
errno = 0;
long val_signed = strtol(s, &endptr_signed, base);
int errno_signed = errno;
char *endptr_unsigned;
errno = 0;
unsigned long val_unsigned = strtoul(s, &endptr_unsigned, base);
int errno_unsigned = errno;
if (val_signed < 0 || (unsigned long) val_signed != val_unsigned
|| endptr_signed != endptr_unsigned || errno_signed != errno_unsigned) {
printf(" signed val:%*ld end:%2td e:%s\n", width, val_signed,
endptr_signed - s, strerror(errno_signed));
printf("unsigned val:%*lu end:%2td e:%s\n", width, val_unsigned,
endptr_unsigned - s, strerror(errno_unsigned));
return 1;
}
printf(" both val:%*ld end:%2td e:%s\n", width, val_signed,
endptr_signed - s, strerror(errno_signed));
return 0;
}
int main() {
char s[][50] = {"-ULONG_MAX1", "-ULONG_MAX", "LONG_MIN1", "LONG_MIN", "-1",
"-0", "42", "LONG_MAX", "LONG_MAX1", "ULONG_MAX", "ULONG_MAX1", "x"};
snprintf(s[0], sizeof *s, "-%lu", ULONG_MAX);
s[0][strlen(s[0]) - 1]++;
snprintf(s[1], sizeof *s, "-%lu", ULONG_MAX);
snprintf(s[2], sizeof *s, "%ld", LONG_MIN);
s[2][strlen(s[2]) - 1]++;
snprintf(s[3], sizeof *s, "%ld", LONG_MIN);
snprintf(s[7], sizeof *s, "%ld", LONG_MAX);
snprintf(s[8], sizeof *s, "%ld", LONG_MAX);
s[8][strlen(s[8]) - 1]++;
snprintf(s[9], sizeof *s, "%lu", ULONG_MAX);
snprintf(s[10], sizeof *s, "%lu", ULONG_MAX);
s[10][strlen(s[10]) - 1]++;
strcpy(s[11], s[0]);
s[11][strlen(s[11]) - 1]++;
int n = sizeof s / sizeof s[0];
for (int i = 0; i < n; i++) {
strtol_test(s[i], 0);
}
}
Sample output
base: 0 "-18446744073709551616"
signed val:-9223372036854775808 end:21 e:Numerical result out of range
unsigned val:18446744073709551615 end:21 e:Numerical result out of range
base: 0 "-18446744073709551615"
signed val:-9223372036854775808 end:21 e:Numerical result out of range
unsigned val: 1 end:21 e:No error
base: 0 "-9223372036854775809"
signed val:-9223372036854775808 end:20 e:Numerical result out of range
unsigned val: 9223372036854775807 end:20 e:No error
base: 0 "-9223372036854775808"
signed val:-9223372036854775808 end:20 e:No error
unsigned val: 9223372036854775808 end:20 e:No error
base: 0 "-1"
signed val: -1 end: 2 e:No error
unsigned val:18446744073709551615 end: 2 e:No error
base: 0 "-0"
both val: 0 end: 2 e:No error
base: 0 "42"
both val: 42 end: 2 e:No error
base: 0 "9223372036854775807"
both val: 9223372036854775807 end:19 e:No error
base: 0 "9223372036854775808"
signed val: 9223372036854775807 end:19 e:Numerical result out of range
unsigned val: 9223372036854775808 end:19 e:No error
base: 0 "18446744073709551615"
signed val: 9223372036854775807 end:20 e:Numerical result out of range
unsigned val:18446744073709551615 end:20 e:No error
base: 0 "18446744073709551616"
signed val: 9223372036854775807 end:20 e:Numerical result out of range
unsigned val:18446744073709551615 end:20 e:Numerical result out of range
base: 0 "-18446744073709551617"
signed val:-9223372036854775808 end:21 e:Numerical result out of range
unsigned val:18446744073709551615 end:21 e:Numerical result out of range
Upvotes: 2
Views: 522
Reputation: 154230
A range error result is always ULONG_MAX
for strtoul()
.
This is unlike strtol()
whose return value is LONG_MIN
or LONG_MAX
, depending on the sign of the range error.
It is fairly simple, for strings like "-18446744073709551616"
, since the correct (positive) value is outside the range, ULONG_MAX
is returned and errno
is set to ERANGE
. No provision exists on a range error for strtoul()
to provide any other answer.
... If the correct value is outside the range of representable values, ...
ULONG_MAX
, ... is returned (according to the return type and sign of the value, if any), and the value of the macroERANGE
is stored inerrno
. C17dr § 7.22.1.4 8
Thus strtoul()
produces a non-error value when the string is in the [-ULONG_MAX ... +ULONG_MAX]
range. The converted result is an unsigned long
[0 ... ULONG_MAX]
. An error result is always ULONG_MAX
.
Upvotes: 5