C: validating numeric input with strtol
Accept only text that is entirely a number in the allowed range, and reject everything else.
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
static int parse_port(const char *text, int *out) {
if (text[0] == '\0' || isspace((unsigned char)text[0])) {
return 0;
}
char *end;
errno = 0;
long value = strtol(text, &end, 10);
if (*end != '\0' || errno == ERANGE || value < 1 || value > 65535) {
return 0;
}
*out = (int)value;
return 1;
}
int main(void) {
const char *inputs[] = {"8080", "0", "70000", "80a", "", " 22", "99999999999999999999"};
for (int i = 0; i < 7; i++) {
int port;
if (parse_port(inputs[i], &port)) {
printf("'%s' -> %d\n", inputs[i], port);
} else {
printf("'%s' -> rejected\n", inputs[i]);
}
}
return 0;
}'8080' -> 8080 '0' -> rejected '70000' -> rejected '80a' -> rejected '' -> rejected ' 22' -> rejected '99999999999999999999' -> rejected
Explanation
Input from users, files, and the network cannot be trusted. Validate it: check its form, that the whole text was used, and that the value is in range. atoi is unsafe for this because it cannot report errors at all. It returns 0 for text such as abc.
strtol converts text to a long and reports where it stopped through an end pointer. The text is a complete number only if the end pointer reaches the terminating zero. errno becomes ERANGE if the value does not fit in a long. strtol also skips leading white space, so a strict validator rejects that explicitly.
Explore the dry run
#include <ctype.h>#include <errno.h>#include <stdio.h>#include <stdlib.h> static int parse_port(const char *text, int *out) { if (text[0] == '\0' || isspace((unsigned char)text[0])) { return 0; } char *end; errno = 0; long value = strtol(text, &end, 10); if (*end != '\0' || errno == ERANGE || value < 1 || value > 65535) { return 0; } *out = (int)value; return 1;} int main(void) { const char *inputs[] = {"8080", "0", "70000", "80a", "", " 22", "99999999999999999999"}; for (int i = 0; i < 7; i++) { int port; if (parse_port(inputs[i], &port)) { printf("'%s' -> %d\n", inputs[i], port); } else { printf("'%s' -> rejected\n", inputs[i]); } } return 0;}Follow the example
Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.
View the complete dry-run table
| Step / line | What happens | State after step | Output so far |
|---|---|---|---|
| Reject empty text and leading white space up front, since strtol would skip the spaces. | — | — | |
| Convert in base 10; end points at the first character that was not used. | — | — | |
| Reject trailing characters, overflow, and out-of-range values. | — | — | |
| Only 8080 passes all checks. | — | '8080' -> 8080 '0' -> rejected '70000' -> rejected '80a' -> rejected '' -> rejected ' 22' -> rejected '99999999999999999999' -> rejected |
Cost and common mistakes
O(n) for n characters.
- errno must be set to 0 before the call, because strtol only sets it on failure.
- Cast the char to unsigned char before passing it to isspace, or negative values cause undefined behaviour.
- A valid number is still untrusted. Use it only for its intended purpose and range.
Try it yourself
Why does '80a' fail?
Reveal answer and reasoning
strtol stops at the letter a, so end does not point to the terminating zero, which means part of the text was not a number.