how to correctly sscanf a decimal to a char - c

PVS-Studio gave me a warning about this :
char c;
sscanf(line, "%d", &c);
I changed %d to %c but this created a bug because "c" now contains the ASCII value of the number and not the decimal one, so I went back to "%d".
So what's the correct specifier to ? is there another solution ?

c is a char. You asked to scan an int. PVS-Studio did right in warning you. Change the type of c to int and scan for a %d.

There are multiple solutions for your problem:
you can specify the correct destination type:
char c;
if (sscanf(line, "%hhd", &c) == 1) {
/* successful conversion */
...
}
you can use an intermediary variable:
char c;
int cc;
if (sscanf(line, "%d", &cc) == 1) {
/* successful conversion */
c = cc;
...
}
you can use different conversion function:
#include <stdlib.h>
...
char c;
c = atoi(line); // no error handling, return 0 if not a number
Note however that in all cases, if the numeric value converted by sscanf() or atoi() is outside the range of type char, the behavior is undefined. Most current system will just use the low order byte of the conversion result, but the C Standard does not guarantee it.

Related

Narrowing conversion from 'long' to signed type 'char' is implementation-defined (strtol function in C)

I'm trying to convert an inputted character to an integer by using strtol. Here's part of the source code:
char option;
char *endptr;
printf("=========================================Login or Create Account=========================================\n\n");
while(1) {
printf("Welcome to the Bank management program! Would you like to 1. Create Account or 2. Login?\n>>> ");
fgets(&option, 1, stdin);
cleanStdinBuffer();
option = strtol(&option, &endptr, 10);
In the strtol function, I'm getting a warning saying:
Clang-Tidy: Narrowing conversion from 'long' to signed type 'char' is implementation-defined
Can someone tell me what I'm doing wrong?
Clang-Tidy is warning you about the implicit conversion you are doing here where you are assign the long return value of strtol to a char:
option = strtol(&option, &endptr, 10);
If this is intentional and you are sure the value will be in the [-128,127] range that isn't necessarily an issue (it's just a warning), but even then I would advice to explicitly cast the return-type of strtol, use int8_t instead of char and not reuse the option variable for the return value. In other words:
int8_t value = (int8_t)strtol(&option, &endptr, 10);
If it wasn't intentional I would recommend you to simply use long as type for the variable you assign the return value of strtol, so:
long value = strtol(&option, &endptr, 10);
What Clang-tidy doesn't warn you about is that the first argument to strtol should be a pointer to a char buffer containing a 0-terminated string, not a pointer to a single char. This is also an issue for fgets. There are two ways to solve this, either:
Make option a char array of at least two chars,
Use fgetc instead and modify your code into something like this:
int option = fgetc(stdin);
if (option == '1') {
/*Create Account */
} else if (option == '2') {
/* Login */
}
else {
/* Error */
}
I think the latter looks much cleaner.
char can only hold a very little subset of the long values. strtol returns long and you assign it to char.
This call of fgets
fgets(&option, 1, stdin);
always sets the character option to the terminating zero character '\0' provided that the user did not interrupt the input.
Here is a demonstrative program.
#include <stdio.h>
int main(void)
{
char c = 'A';
printf( "Before calling fgets c = %d\n", c );
fgets( &c, 1, stdin );
printf( "After calling fgets c = %d\n", c );
return 0;
}
The program output is
Before calling fgets c = 65
After calling fgets c = 0
independent on what the user will enter. Here is the value 65 is the ASCII code of the character 'A' that was stored in the variable c before calling fgets.
If you want to enter a character then you should use
scanf( " %c", &input );
Pay attention to the blank before the conversion specifier.
After this call you can check whether the user typed a digit like
#include <ctypes.h>
//...
if ( isdigit( ( unsigned char )input ) ) input = input - '0';
and set the variable input to the corresponding integer value in the range [0, 9].

How to check an edge case in taking command line argument in C and evaluating to int or double?

So I have an assignment to figure out whether a number on the command line is either an integer or a double.
I have it mostly figured it out by doing:
sscanf(argv[x], "%lf", &d)
Where "d" is a double. I then cast it to an int and then subtract "d" with itself to check to see if it is 0.0 as such.
d - (int)d == 0.0
My problem is if the command line arguments contains doubles that can be technically classified as ints.
I need to classify 3.0 as a double whereas my solution considers it an int.
For example initializing the program.
a.out 3.0
I need it to print out
"3.0 is a double"
However right now it becomes
"3 is an int."
What would be a way to check for this? I did look around for similar problems which led me to the current solution but just this one edge case I do not know how to account for.
Thank you.
For example, a way like this:
#include <stdio.h>
int main(int argc, char *argv[]){
if(argc != 2){
puts("Need an argument!");
return -1;
}
int int_v, read_len = 0;
double double_v;
printf("'%s' is ", argv[1]);
//==1 : It was able to read normally.
//!argv[1][read_len] : It used all the argument strings.
if(sscanf(argv[1], "%d%n", &int_v, &read_len) == 1 && !argv[1][read_len])
puts("an int.");
else if(sscanf(argv[1], "%lf%n", &double_v, &read_len) == 1 && !argv[1][read_len])
puts("a double.");
else
puts("isn't the expected input.");
}
To test if a string will covert to a int and/or double (completely, without integer overflow, without undefined behavior), call strtol()/strtod(). #Tom Karzes
The trouble with a sscanf() approach is that the result is undefined behavior (UB) on overflow. To properly detect, use strtol()/strtod().
#include <errno.h>
#include <limits.h>
#include <stdbool.h>
#include <stdlib.h>
bool is_int(const char *src) {
char *endptr;
// Clear, so it may be tested after strtol().
errno = 0;
// Using 0 here allows 0x1234, octal 0123 and decimal 1234.
// or use 10 to allow only decimal text.
long num = strtol(src, &endptr, 0 /* or 10 */);
#if LONG_MIN < INT_MIN || LONG_MAX > INT_MAX
if (num < INT_MIN || num > INT_MAX) {
errno = ERANGE;
}
#endif
return !errno && endptr > src && *endptr == '\0';
}
bool is_double(const char *src) {
char *endptr;
// Clear, so it may be tested after strtod().
strtod(src, &endptr);
// In this case, detecting over/underflow IMO is not a concern.
return endptr > src && *endptr == '\0';
}
It is not entirely clear what the specific expectations are for your program, but it has at least something to do with the form of the input, since "3.0" must be classified as a double. If the form is all it should care about, then you should not try to convert the argument strings to numbers at all, for then you will run into trouble with unrepresentable values. In that case, you should analyze the character sequence of the argument to see whether it matches the pattern of an integer, and if not, whether it matches the pattern of a floating-point number.
For example:
int main(int argc, char *argv[]) {
for (int arg_num = 1; arg_num < argc; arg_num++) {
char *arg = argv[arg_num];
int i = (arg[0] == '-' || arg[0] == '+') ? 1 : 0; // skip any leading sign
// scan through all the decimal digits
while(isdigit(arg[i])) {
++i;
}
printf("Argument %d is %s.\n", arg_num, arg[i] ? "floating-point" : "integer");
}
}
That makes several assumptions, chief among them:
the question is strictly about form, so that the properties of your system's built-in data types (such as int and double) are not relevant.
each argument will have the form of either an integer or a floating-point number, so that eliminating "integer" as a possibility leaves "floating-point" as the only alternative. If "neither" is a possibility that must also be accommodated, then you'll also need to compare the inputs that do not have integer form to a pattern for floating-point numbers, too.
only decimal (or smaller radix) integers need be accommodated -- not, for example, hexadecimal inputs.
Under those assumptions, particularly the first, it is not just unnecessary but counterproductive to attempt to convert the arguments to one of the built-in numeric data types, because you would then come to the wrong conclusion about arguments that, say, are not within the bounds of representable values for those types.
For example, consider how the program should classify "9000000000". It has the form of an integer, but supposing that your system's int type has 31 value bits, that type cannot accommodate a value as large as the one the string represents.
int main (int argc,char *argv[])
{
if(argc==2)
{
int i;
double d;
d=atof(argv[1]);
i=atoi(argv[1]);
if(d!=i)
printf("%s is a double.",argv[1]);
else if(d==i)
printf("%s is an int.",argv[1]);
}
else
printf("Invalid input\n");
return 0;
}
You must add #include <stdlib.h>

variable is losing its value in c program

This is written in 'c' and compiled with gcc. I'm not sure what else you need to know.
The smallest complete example I could put together is shown here. The variable 'numatoms' loses its value when it gets to line 23 (after the scanf()).
I'm stumped. Maybe it has something to do with scanf() overwritting the space for numatoms?
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
/*
*
*/
int main(int argc, char** argv) {
uint8_t numatoms;
uint8_t r;
char a[20];
do {
printf("NO. OF ATOMS?");
fflush(stdout);
scanf("%d", &numatoms);
printf("\r\n");
for(;;){
printf("value of numatoms is %u\r\n", numatoms);
printf("RAY?");
fflush(stdout);
scanf("%u", &r);
printf("value of numatoms is %u\r\n", numatoms);
if(r < 1)
break;
else {
printf("value of numatoms is %u\r\n", numatoms);
}
}
printf("CARE TO TRY AGAIN?");
fflush(stdout);
scanf("%s", a);
printf("\r\n");
} while (a[0] == 'y' || a[0] == 'Y');
return (EXIT_SUCCESS);
}
uint8_t is 8 bits long %u reads an unsigned int (probably 32 bits long).
You either need to make numatoms "bigger" (i.e. unsigned int) or read the correct size (see scanf can't scan into inttypes (uint8_t))
You should use macros for format specifiers for integer types defined in the header <inttypes.h>.
From the C Standard (7.8.1 Macros for format specifiers)
1 Each of the following object-like macros expands to a character
string literal containing a conversion specifier, possibly modified by
a length modifier, suitable for use within the format argument of a
formatted input/output function when converting the corresponding
integer type. These macro names have the general form of PRI
(character string literals for the fprintf and fwprintf family) or SCN
(character string literals for the fscanf and fwscanf family),217)
followed by the conversion specifier, followed by a name corresponding
to a similar type name in 7.20.1. In these names, N represents the
width of the type as described in 7.20.1.
The general form of the macro used for unsigned integer types with the conversion specifier u looks like
SCNuN
Here is a demonstrative program
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
int main(void)
{
uint8_t x;
scanf( "%" SCNu8, &x );
printf( "x = %u\n", x );
return 0;
}

What happens when using scanf("%d", &c) while c is a char?

the code is like this:
char c;
scanf("%d", &c);
inputting 3...
My guess is that when 3 is inputted, it is as type int;
and then type-demoted to char and assigned to c;
I print the value of c in specifier %d yielding 3, seems to be as expected;
but printing the value of c with specifier %c yields --a blank-- on the terminal;this is one question...(1);
to test more I furthermore declare a variable ch with type char and initialized it like this:
char ch = 1;
and the test is like this:
(i&j)? printf("yes") : printf("no");
and the result is "no"
I print out the value of i&j, and it is 0
but 1&3 should be 1? this is another question....(2);
my question is (1) and (2)
You're actually invoking undefined behavior doing that.
By using the format string %d, you're telling scanf to expect an int* as a parameter and you're passing it a pointer to a single character. Remember that scanf has no further type information on what you're passing it than what you're putting in the format string.
This will result in scanf attempting to write an int sized value to memory at an address that points to a char sized reservation, potentially (and for most architectures) writing out of bounds.
After invoking UB, all bets are off on your further calculations.
Suppose that scanf() were not a varargs-function, but a plain ordinary function taking a pointer-to-int as the 2nd argument:
int noscanf(char *format, int *ptr)
{
*ptr = 42;
return 1;
}
int main(void)
{
char ch;
int rc;
// This should *at least* give a warning ...
rc = noscanf("Haha!" , &ch);
return 0;
}
Now, scanf() is a varargs function. The only way for scanf() to determine the type of the (pointer) arguments is by inspecting the format string. And a %d means : the next argument is supposed to be a pointer to int. So scanf can happily write sizeof(int) bytes to *ptr.
I can't see a variable jthere. So i&j will be 0. And yes, if i == 1 and j == 3 then i & j == 1.
(i&j)? printf("yes") : printf("no");
statement gives the output yes,for i=1 and j=3.
And for (1) question ASCII 3 is for STX char which is not printable.

C char appears as int when compiling

I'm learning C, and have been trying to make a program that takes user input, and removes any double spaces in it, then prints it out again. We have not done arrays yet so I need to do this char by char. This is my code:
#include <stdio.h>
main()
{
char c;
int count;
count = 0;
while ((c = getchar()) != '\n')
if (c == ' ')
count++;
if (c != ' ')
count = 0;
if (count <= 0)
printf("%s", c);
}
This code does not work, however. The compiler returns the error
:15: warning: format ‘%s’ expects type ‘char *’, but argument 2 has type ‘int’
Any help? I have no clue what I am doing wrong.
Use the %c format specifier to print a single char
printf("%c", c);
The %s format specifier tells printf to expect a null-terminated char array (aka a string).
The error message refers to c having type int due to default promotion of arguments (beyond the format string) passed to printf. This previous answer has a nice description of default promotion; this previous thread explains some of the reasoning for why default promotion is necessary.
You are using %s which is used for a string and which expects a terminating NULL character(\0)..
Using %c will print you char by char..
Your code has so many problems
First, you print a char with %s (which expects a char pointer, i.e. a string)
In C, char literals are of type int, so whether promoted or not, they're always int. In C++, char literals would be of type char, but after promotion like other answers said, again they'll be int. A plain char variable will also be promoted to int in expressions, and passed as int in vararg functions like printf. That's why the compiler warns you that argument 2 has type ‘int’, because it's expecting a char* and you're passing it an int
→ You must use %c to print a char
Your while loop's body is only the first if block, because in C block scope is defined by {}, not by indentation. So the code will run like this, which is not like what you intended
while ((c = getchar()) != '\n')
{
if (c == ' ')
count++;
}
if (c != ' ')
count = 0;
if (count <= 0)
printf("%s", c);
→ You need to put the code block in a pair of brackets. And using else instead of 2 separate ifs to make it more readable and faster (for dumb compilers)
while ((c = getchar()) != '\n')
{
if (c == ' ')
count++;
else
count = 0;
if (count <= 0)
printf("%s", c);
}
main() is wrong. The correct versions in C would be
int main(void)
int main(int argc, char **argv)
See What should main() return in C and C++?
c must be declared as int because getchar returns int. See Why must the variable used to hold getchar's return value be declared as int?
A minor point is that instead of int count; count = 0;, just initialize the variable while declaring int count = 0. Or better yet use an unsigned int, because count can't be negative

Resources