How I can handle integer overflow? - c

I am trying to handle integer overflow. My code is :
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<limits.h>
int isInt (char *s)
{
char *ep = NULL;
long i = strtol (s, &ep, 10);
if ((*ep == 0) || (!strcmp(ep,"\n")))
return 1; // it's an int
return 0;
}
int main()
{
char *buffer = NULL;
size_t count = 0;
ssize_t ret;
//AMINO *a_acid;
int num;
for(;;)
{
printf("Please enter an integer:");
if((ret = getline(&buffer, &count, stdin)) < 0)
{
perror("getline: error\n");
free(buffer);
exit(EXIT_FAILURE);
}
if(!isInt(buffer))
{
perror("you are not entering int , Try again:");
continue;
}
sscanf(buffer, "%d",&num);
printf("%d\n", num);
if ((num > INT_MAX)|| (num < 0))
{
perror("you overflowed int variable , Try again:\n ");
continue;
}
break;
}
}
Now I was checking how this code is responding. And I saw something weird.When I am entering so big number, then it is detected. But sometimes is not getting detected.
Here is my terminal view:
> nazmul#nazmul-Lenovo-G50-80:~/2nd_sem/biophysics$ gcc torson.c
> nazmul#nazmul-Lenovo-G50-80:~/2nd_sem/biophysics$ ./a.out
> Please enter an integer:ksdjfjklh
> you are not entering int , Try again:: Success
> Please enter an integer:338479759475637465765
> -1
> you overflowed int variable , Try again: : Numerical result out of
> range
> Please enter an integer:58678946895785
> 1103697833
> nazmul#nazmul-Lenovo-G50-80:~/2nd_sem/biophysics$
*Why it is working for this number 338479759475637465765. But it is not working for 58678946895785. logic , I used in my program, is when it is out of bound, then int variable gives some -1 or negative value. I read many article, still it is not quite clear.

strtol converts the value to a long int, whose range might be distinct from int. Furthermore, it returns LONG_MAX or LONG_MIN if the value could be converted but is outside the range for long int. In that case, errno will be set to ERANGE (but not otherwise!) Also, in the case of matching failure the value returned is 0, but errno is not set; but the ep points to the beginning of the string.
int isInt (char *s)
{
char *ep = NULL;
// zero errno first!
errno = 0;
long i = strtol (s, &ep, 10);
if (errno) {
return 0;
}
// matching failure.
if (ep == s) {
return 0;
}
// garbage follows
if (! ((*ep == 0) || (!strcmp(ep,"\n")))) {
return 0;
}
// it is outside the range of `int`
if (i < INT_MIN || i > INT_MAX) {
return 0;
}
return 1;
}
What dbush says about the use of perror is correct, though. strtol sets an error only in case of long overflow, which is not the only possible failing case in your function, so perror could print anything like Is a directory or Multihop attempted.

sscanf(buffer, any_format_without_width, &anytype); is not sufficient to detect overflow.
if the result of the conversion cannot be represented in the object, the behavior is undefined. C11dr §7.21.6.2 10
Do not use *scanf() family to detect overflow. It may work in select cases, but not in general.
Instead use strto**() functions. Yet even OP's isInt() is mis-coded as it incorrectly assess isInt("\n"), isInt(""), isInt("999..various large values ...999") as good ints.
Alternative:
bool isint_alt(const char *s) {
char *endptr;
errno = 0;
long y = strtol(s, &endptr, 10);
if (s == endptr) {
return false; // No conversion
}
if (errno == ERANGE) {
return false; // Outside long range
}
if (y < INT_MIN || y > INT_MAX) {
return false; // Outside int range
}
// Ignore trailing white space
while (isspace((unsigned char)*endptr)) {
endptr++;
}
if (*endptr) {
return false; // Trailing junk
}
return true;
}

You're getting your types mixed up.
In the isInt function you use strtol, which return a long to check the value. Then in your main function you use sscanf with %d, which reads into an int.
On your system, it seems that a long is 64 bits while an int is 32 bits. So strtol fails to fully convert 338479759475637465765 because it is larger than a 64 bit variable can hold. Then you try to convert 58678946895785 which will fit in a 64 bit variable but not a 32 bit variable.
You should instead have sscanf read into a long. Then you can compare the value against INT_MAX:
long num;
...
sscanf(buffer, "%ld", &num);
printf("%ld\n", num);
if ((num > INT_MAX)|| (num < INT_MIN))
{
printf("you overflowed int variable , Try again:\n ");
continue;
}
Also note that it doesn't make sense to call perror here. You only use it right after calling a function which sets errno.

If one must use sscanf() to detect int overflow rather than the robust strtol(), there is a cumbersome way.
Use a wider type and a width limit to prevent overflow when scanning.
bool isint_via_sscanf(const char *s) {
long long y;
int n = 0;
if (sscanf(s, "18%lld %n", &y, &n) != 1) { // Overflow not possible
return false; // Conversion failed
}
if (y < INT_MIN || y > INT_MAX) {
return false; // Outside int range
}
if (s[n]) {
return false; // Trailing junk
}
return true;
}
It is insufficient on rare platforms where INT_MAX > 1e18.
It also incorrectly returns input like "lots of leading space and/or lot of leading zeros 000123" as invalid.
More complex code using sscanf() can address these short-comings, yet the best approach is strto*().

Related

errno NOT being set to ERANGE after converting a string with a HUGE number to a double floating point number using strtod() in C

I am perplexed that errno is not being set to ERANGE after strtod() tries to convert a string with a huge real number to an double floating point number.
Here is what happens when I run my code:
Enter a real number: 5656565656565652622326352635236523652362356235
You entered 5656565656565652358969982685269310483757793280.000000.
Here is my code:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
int getDouble(char* prompt, double* doubleRealNumber);
int main(void)
{
double d;
getDouble("Enter a real number: ", &d);
printf("You entered %lf.\n", d);
return 0;
}
int getDouble(char* prompt, double* doubleRealNumber)
{
int const MAXBUFSIZE = 1024;
char buf[MAXBUFSIZE]; // use 1KiB just to be sure
int success; // flag for successful conversion
do
{
printf("%s", prompt);
fflush(stdout);
if (!fgets(buf, MAXBUFSIZE, stdin))
{
// reading input failed:
return 1;
}
// have some input, convert it to integer:
char *endptr;
errno = 0; // reset error number
*doubleRealNumber = strtod(buf, &endptr);
if (errno == ERANGE)
{
printf("Sorry, this number is too small or too large.\n");
success = 0;
}
else if (endptr == buf)
{
// no character was read
success = 0;
}
else if (*endptr && *endptr != '\n')
{
// *endptr is neither end of string nor newline,
// so we didn't convert the *whole* input
success = 0;
}
else
{
success = 1;
}
} while (!success); // repeat until we got a valid real number
return 0;
}
errno is in fact being set properly to ERANGE when a very large (or very small) number is entered.
If one enters a really big number like "100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", errno is set to ERANGE and the following is printed -- "Sorry, this number is too small or too large." So, the code posted is valid and works properly.
The reason is the input number and the output number do not match in the question is due to a loss of precision rather than out of range.
Not every number within the dynamic range (of the exponent) can be accurately represented (by the mantissa).
Many thanks to Useless and Jabberwocky for pointing this out!

How to use `strtoul` to parse string where zero may be valid?

According to the documentation for strtoul, regarding its return value...
This function returns the converted integral number as a long int value. If no valid conversion could be performed, a zero value is returned.
What if I'm parsing a user-supplied string of "0" where, for my application, "0" may be a valid entry? In that case it seems that I have no way to determine from using strtoul if a valid conversion was performed. Is there another way to handle this?
Read further the man page:
Since strtoul() can legitimately return 0 or ULONG_MAX (ULLONG_MAX for strtoull()) on both success and failure, the calling program should set errno to 0 before the call, and then determine if an error occurred by checking whether errno has a nonzero value after the call.
Also, to handle another scenario, where no digits were read in the input. If this happens, strtol() sets the value of *endptr to that of the nptr. So, you should also check that the pointer values compare equal or not.
How to use strtoul to parse string where zero may be valid?
Any value returned from strtoul() may be from an expected string input or from other not so expected strings. Further tests are useful.
The following strings all return 0 from strtoul()
OK "0", "-0", "+0"
Not OK "", "abc"
Usually considered OK: " 0"
OK or not OK depending on goals: "0xyz", "0 ", "0.0"
strtoul() has the various detection modes.
int base = 10;
char *endptr; // Store the location where conversion stopped
errno = 0;
unsigned long y = strtoul(s, &endptr, base);
if (s == endptr) puts("No conversion"); // "", "abc"
else if (errno == ERANGE) puts("Overflow");
else if (*endptr) puts("Extra text after the number"); // "0xyz", "0 ", "0.0"
else puts("Mostly successful");
What is not yet detected.
Negative input. strtoul() effectively wraps around such that strtoul("-1", 0, 10) == ULONG_MAX). This issue is often missed in cursory documentation review.
Leading white space allowed. This may or may not be desired.
To also detect negative values:
// find sign
while (isspace((unsigned char) *s)) {
s++;
}
char sign = *s;
int base = 10;
char *endptr; // Store the location where conversion stopped
errno = 0;
unsigned long y = strtoul(s, &endptr, base);
if (s == endptr) puts("No conversiosn");
else if (errno == ERANGE) puts("Overflow");
else if (*endptr) puts("Extra text after the number");
else if (sign == '-' && y != 0) puts("Negative value");
else puts("Successful");
One solution would be to pass the address of a char pointer and check if it is pointing to the beginning of the string:
char *str = "0";
char *endptr;
unsgined long x = strtoul(str, &endptr, 10);
if(endptr == str)
{
//Nothing was read
}
Consider the following function:
#include <stdlib.h>
#include <errno.h>
/* SPDX-Identifier: CC0-1.0 */
const char *parse_ulong(const char *src, unsigned long *to)
{
const char *end;
unsigned long val;
if (!src) {
errno = EINVAL;
return NULL;
}
end = src;
errno = 0;
val = strtoul(src, (char **)(&end), 0);
if (errno)
return NULL;
if (end == src) {
errno = EINVAL;
return NULL;
}
if (to)
*to = val;
return end;
}
This function parses the unsigned long in the string src, returning a pointer to the first unparsed character in src, with the unsigned long saved to *to. If there is an error, the function will return NULL with errno set to indicate the error.
If you compare the function to man 3 strtoul, you'll see it handles all error cases correctly, and only returns non-NULL when src yields a valid unsigned long. Especially see the Notes section. Also pay attention to how negative numbers are handled.
This same pattern works for strtol(), strtod(), strtoull().

Using isdigit with if

int main()
{
int f;
printf("Type your age");
scanf("%d", &f);
if(!isdigit(f))
{
printf("Digit");
}
else
{
printf("Is not a digit");
}
return 0;
}
No matter if a typed 6 or a always shows me the "Digit" message
isdigit() should be passed a char not an int. And your if-else logic is reversed:
int main() {
char f;
printf("Type your age");
scanf("%c", &f);
if (isdigit(f)) {
printf("Digit");
} else {
printf("Is not a digit");
}
return 0;
}
As mentioned in the comments, this will only work for a single digit age. Validating input is a major topic under the 'C' tag, a search will reveal many approaches to more robust validation.
%d is an integer specifier. Change int f to char f and parse as a character. You are always passing an int into isdigit, which is why it is always true.
There's actually no need to use isdigit at all here since scanf with the %d format specifier already guarantees that the characters will be digits with an optional leading sign. And there's a separate specifier to get rid of the leading sign, %u.
If what you input isn't of the correct format, scanf will tell you (since it returns the number of items successfully scanned).
So, for a simple solution, you can just use something like:
unsigned int age;
if (scanf("%u", &age) == 1) {
puts("Not a valid age");
return 1;
}
// Now it's a valid uint, though you may want to catch large values.
If you want robust code, you may have to put in a little more effort than a one-liner scanf("%d") - it's fine for one-time or throw-away programs but it has serious shortcomings for code intended to be used in real systems.
First, I would use the excellent string input routine in this answer(a) - it pretty much provides everything you need for prompted and checked user input.
Once you have the input as a string, strtoul allows you to do the same type of conversion as scanf but with the ability to also ensure there's no trailing rubbish on the line as well. This answer (from the same author) provides the means for doing that.
Tying that all together, you can use something like:
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
// Code to robustly get input from user.
#define OK 0 // Return codes - okay.
#define NO_INPUT 1 // - no input given.
#define TOO_LONG 2 // - input was too long.
static int getLine (
char *prmpt, // The prompt to use (NULL means no prompt).
char *buff, // The buffer to populate.
size_t sz // The size of the buffer.
) {
int ch, extra;
// Get line with buffer overrun protection.
if (prmpt != NULL) {
printf ("%s", prmpt);
fflush (stdout);
}
if (fgets (buff, sz, stdin) == NULL)
return NO_INPUT;
// If it was too long, there'll be no newline. In that case, we flush
// to end of line so that excess doesn't affect the next call.
if (buff[strlen(buff)-1] != '\n') {
extra = 0;
while (((ch = getchar()) != '\n') && (ch != EOF))
extra = 1;
return (extra == 1) ? TOO_LONG : OK;
}
// Otherwise remove newline and give string back to caller.
buff[strlen(buff)-1] = '\0';
return OK;
}
// Code to check string is valid unsigned integer and within range.
// Returns true if it passed all checks, false otherwise.
static int validateStrAsUInt(
char *str, // String to evaluate.
unsigned int minVal, // Minimum allowed value.
unsigned int maxVal, // Maximum allowed value.
unsigned int *pResult // Address of item to take value.
) {
char *nextChar;
unsigned long retVal = strtoul (str, &nextChar, 10);
// Ensure we used the *whole* string and that it wasn't empty.
if ((nextChar == str) || (*nextChar != '\0'))
return 0;
// Ensure it's within range.
if ((retVal < minVal) || (retVal > maxVal))
return 0;
// It's okay, send it back to caller.
*pResult = retVal;
return 1;
}
// Code for testing above functions.
int main(void) {
int retCode;
unsigned int age;
char buff[20];
// Get it as string, detecting input errors.
retCode = getLine ("Enter your age> ", buff, sizeof(buff));
if (retCode == NO_INPUT) {
printf ("\nError, no input given.\n");
return 1;
}
if (retCode == TOO_LONG) {
printf ("Error, input too long [%s]\n", buff);
return 1;
}
// Check string is valid age.
if (! validateStrAsUInt(buff, 0, 150, &age)) {
printf("Not a valid age (0-150)\n");
return 1;
}
// It's okay, print and exit.
printf("Age is valid: %u\n", age);
return 0;
}
(a) I'm reliably informed the author is actually quite clever, and very good looking :-)

strtol not changing errno

I'm working on a program that performs calculations given a char array that represents a time in the format HH:MM:SS. It has to parse the individual time units.
Here's a cut down version of my code, just focusing on the hours:
unsigned long parseTime(const char *time)
{
int base = 10; //base 10
long hours = 60; //defaults to something out of range
char localTime[BUFSIZ] //declares a local array
strncpy(localTime, time, BUFSIZ); //copies parameter array to local
errno = 0; //sets errno to 0
char *par; //pointer
par = strchr(localTime, ':'); //parses to the nearest ':'
localTime[par - localTime] = '\0'; //sets the ':' to null character
hours = strtol(localTime, &par, base); //updates hours to parsed numbers in the char array
printf("errno is: %d\n", errno); //checks errno
errno = 0; //resets errno to 0
par++; //moves pointer past the null character
}
The problem is that if the input is invalid (e.g. aa:13:13), strtol() apparently doesn't detect an error because it's not updating errno to 1, so I can't do error handling. What am I getting wrong?
strtol is not required to produce an error code when no conversion can be performed. Instead you should use the second argument which stores the final position after conversion and compare it to the initial position.
BTW there are numerous other errors in your code that do not affect the problem you're seeing but which should also be fixed, such as incorrect use of strncpy.
As others have explained, strtol may not update errno in case it cannot perform any conversion. The C Standard only documents that errnor be set to ERANGE in case the converted value does not fit in a long integer.
Your code has other issues:
Copying the string with strncpy is incorrect: in case the source string is longer than BUFSIZ, localTime will not be null terminated. Avoid strncpy, a poorly understood function that almost never fits the purpose.
In this case, you no not need to clear the : to '\0', strtol will stop at the first non digit character. localTime[par - localTime] = '\0'; is a complicated way to write *par = '\0';
A much simpler version is this:
long parseTime(const char *time) {
char *par;
long hours;
if (!isdigit((unsigned char)*time) {
/* invalid format */
return -1;
}
errno = 0;
hours = strtol(time, &par, 10);
if (errno != 0) {
/* overflow */
return -2;
}
/* you may want to check that hour is within a decent range... */
if (*par != ':') {
/* invalid format */
return -3;
}
par++;
/* now you can parse further fields... */
return hours;
}
I changed the return type to long so you can easily check for invalid format and even determine which error from a negative return value.
For an even simpler alternative, use sscanf:
long parseTime(const char *time) {
unsigned int hours, minutes, seconds;
char c;
if (sscanf(time, "%u:%u:%u%c", &hours, &minutes, &seconds, &c) != 3) {
/* invalid format */
return -1;
}
if (hours > 1000 || minutes > 59 || seconds > 59) {
/* invalid values */
return -2;
}
return hours * 3600L + minutes * 60 + seconds;
}
This approach still accepts incorrect strings such as 1: 1: 1 or 12:00000002:1. Parsing the string by hand seem the most concise and efficient solution.
A useful trick with sscanf() is that code can do multiple passes to detect errant input:
// HH:MM:SS
int parseTime(const char *hms, unsigned long *secs) {
int n = 0;
// Check for valid text
sscanf(hms "%*[0-2]%*[0-9]:%*[0-5]%*[0-9]:%*[0-5]%*[0-9]%n", &n);
if (n == 0) return -1; // fail
// Scan and convert to integers
unsigned h,m,s;
sscanf(hms "%u:%u:%u", &h, &m, &s);
// Range checks as needed
if (h >= 24 || m >= 60 || s >= 60) return -1;
*sec = (h*60 + m)*60L + s;
return 0;
}
After hours = strtol(localTime, &par, base); statement you have to first save the value of errno. Because after this statement you are going to call printf() statement that also set errno accordingly.
printf("errno is: %d\n", errno);
So in this statement "errno" gives the error indication for printf() not for strtol()... To do so save "errno" before calling any library function because most of the library function interact with "errno".
The correct use is :
hours = strtol(localTime, &par, base);
int saved_error = errno; // Saving the error...
printf("errno is: %d\n", saved_error);
Now check it. It will give correct output surely...And one more thing to convert this errno to some meaningful string to represent error use strerror() function as :
printf("Error is: %s\n", strerror(saved_error));

how to know whether input is a number or not?

I have a problem.
My program needs to check whether the input is a number or anything else,in C.I searched and found that isdigit(int) is a function provided for this..but it only checks 48<=ASCII<=57 .
But what if i have to check for numbers only ? Is there any function like isInteger(),isReal(),isFloat() etc? and if not then how can i check for them?
In case of isdigit() too, I am getting a problem:
#include<stdio.h>
#include<ctype.h>
int main(){
int i = 1;
printf("%d",isdigit(i));
return 0;
}
it outputs 0 ,i do not understand ,it should be a non zero value ...
Assuming the input is a string, use strtol() and/or strtod().
Example
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
int main(void) {
char input[1000];
// get input, don't forget to check for errors
if (fgets(input, sizeof input, stdin) == NULL) {
fprintf(stderr, "Error in input\n");
exit(EXIT_FAILURE);
}
char *err;
errno = 0;
long x = strtol(input, &err, 10);
if (errno || *err) {
fprintf(stderr, "input is not an integer in the range for `long`.\n");
exit(EXIT_FAILURE);
}
errno = 0;
double y = strtod(input, &err);
if (errno || *err) {
fprintf(stderr, "input is not a correct double.\n");
exit(EXIT_FAILURE);
}
// use x and y as needed
return 0;
}
Read the documentation of isdigit(3). It applies to characters, not numbers. So isdigit('2') should be true (non-zero). As answered by pmg see also strtol(3)...
If you wanted to test digits of UTF-8 or Unicode it is much more complex. Use a library function, e.g. g_unichar_is_digit from Glib for Unicode.
You might make a fundamental mistake. A number is not made of digits. It is represented or written with digits. So two, 2 (decimal), II (roman notation), 0b10 or 10 (binary), deux (French), 1+1 (arithmetic expression), 二 (Chinese) .... all represent the same number (which is the successor of one), but only the second representation -decimal 2- has one digit.
From isdigit man page:
Check if character is decimal digit
So you can see that this function is applied to characters. Its return value should be non - zero.
You may try this:
bool checkNumber(const char *str)
{
while(*str != '\0')
{
if(*str < '0' || *str > '9')
return false;
str++;
}
return true;
}
Try this man.....
#include<stdio.h>
int main()
{
char a;
printf("enter any int\n");
scanf("%c",&a);
if(isdigit(a))
{
printf("input was a number");
}
else
printf("input wasnt a number");
return 0;
}

Resources