Is char and int interchangeable for function arguments in C? - c++

I wrote some code to verify a serial number is alpha numeric in C using isalnum. I wrote the code assuming isalnum input is char. Everything worked. However, after reviewing the isalnum later, I see that it wants input as int. Is my code okay the way it is should I change it?
If I do need to change, what would be the proper way? Should I just declare an int and set it to the char and pass that to isalnum? Is this considered bad programming practice?
Thanks in advance.
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
bool VerifySerialNumber( char *serialNumber ) {
int num;
char* charPtr = serialNumber;
if( strlen( serialNumber ) < 10 ) {
printf("The entered serial number seems incorrect.");
printf("It's less than 10 characters.\n");
return false;
}
while( *charPtr != '\0' ) {
if( !isalnum(*charPtr) ) {
return false;
}
*charPtr++;
}
return true;
}
int main() {
char* str1 = "abcdABCD1234";
char* str2 = "abcdef##";
char* str3 = "abcdABCD1234$#";
bool result;
result = VerifySerialNumber( str1 );
printf("str= %s, result=%d\n\n", str1, result);
result = VerifySerialNumber( str2 );
printf("str= %s, result=%d\n\n", str2, result);
result = VerifySerialNumber( str3 );
printf("str= %s, result=%d\n\n", str3, result);
return 0;
}
Output:
str= abcdABCD1234, result=1
The entered serial number seems incorrect.It's less than 10 characters.
str= abcdef##, result=0
str= abcdABCD1234$#, result=0

You don't need to change it. The compiler will implicitly convert your char to an int before passing it to isalnum. Functions like isalnum take int arguments because functions like fgetc return int values, which allows for special values like EOF to exist.
Update: As others have mentioned, be careful with negative values of your char. Your version of the C library might be implemented carefully so that negative values are handled without causing any run-time errors. For example, glibc (the GNU implementation of the standard C library) appears to handle negative numbers by adding 128 to the int argument.* However, you won't always be able to count on having isalnum (or any of the other <ctype.h> functions) quietly handle negative numbers, so getting in the habit of not checking would be a very bad idea.
* Technically, it's not adding 128 to the argument itself, but rather it appears to be using the argument as an index into an array, starting at index 128, such that passing in, say, -57 would result in an access to index 71 of the array. The result is the same, though, since array[-57+128] and (array+128)[-57] point to the same location.

Usually it is fine to pass a char value to a function that takes an int. It will be converted to the int with the same value. This isn't a bad practice.
However, there is a specific problem with isalnum and the other C functions for character classification and conversion. Here it is, from the ISO/IEC 9899:TC2 7.4/1 (emphasis mine):
In all cases the argument is an int, the value of which shall be
representable as an unsigned char or shall equal the value of the
macro EOF. If the argument has any other value, the behavior is
undefined.
So, if char is a signed type (this is implementation-dependent), and if you encounter a char with negative value, then it will be converted to an int with negative value before passing it to the function. Negative numbers are not representable as unsigned char. The numbers representable as unsigned char are 0 to UCHAR_MAX. So you have undefined behavior if you pass in any negative value other than whatever EOF happens to be.
For this reason, you should write your code like this in C:
if( !isalnum((unsigned char)*charPtr) )
or in C++ you might prefer:
if( !isalnum(static_cast<unsigned char>(*charPtr)) )
The point is worth learning because at first encounter it seems absurd: do not pass a char to the character functions.
Alternatively, in C++ there is a two-argument version of isalnum in the header <locale>. This function (and its friends) do take a char as input, so you don't have to worry about negative values. You will be astonished to learn that the second argument is a locale ;-)

Related

Conversion from int to char may alter its value

This is giving me an error on line 524:35 where it says
strcap[i] = tolower (str [i]);
saying that the conversion to char from int may alter its value, I have a few errors of these in the source code, so if I can fix this one then the others will be a piece of cake. Can anyone please explain to me in simple terms? I am quite new to this.. Thanks!
char *imccapitalize( const char *str )
{
static char strcap[LGST];
int i;
for( i = 0; str[i] != '\0'; i++ )
strcap[i] = tolower( str[i] );
strcap[i] = '\0';
strcap[0] = toupper( strcap[0] );
return strcap;
}
Many C "character" functions in fact take and return ints as characters. (This is so they can return special values such as EOF, and also because this practice got set in stone before people noticed that type safety was a good thing.) toupper and tolower are two of these functions. The declaration for tolower is:
int tolower(int c);
So to get rid of these warnings, you have to typecast the return value:
strcap[i] = (char) tolower( str[i] );
It should be a warning, not an error (however most compilers have an option to treat warnings
as errors, so if that option is being used an error will be produced).
The warning is generated because tolower returns int, which is common for the C runtime library character manipulation functions. Assigning an int to char can result in truncation of the value since on most machines a char is one-byte and an int is two or more, hence the warning.
A typecast will eliminate the warning:
strcap[i] = (char)tolower(str[i]);
Integers are represented using more bytes than chars do. Therefore if you try to turn an integer to a char you will have to get rid of the extra bytes ,potentially altering the integer's value.

atof and non-null terminated character array

using namespace std;
int main(int argc, char *argv[]) {
char c[] = {'0','.','5'};
//char c[] = "0.5";
float f = atof(c);
cout << f*10;
if(c[3] != '\0')
{
cout << "YES";
}
}
OUTPUT: 5YES
Does atof work with non-null terminated character arrays too? If so, how does it know where to stop?
Does atof work with non-null terminated character arrays too?
No, it doesn't. std::atof requires a null-terminated string in input. Failing to satisfy this precondition is Undefined Behavior.
Undefined Behavior means that anything could happen, including the program seeming to work fine. What is happening here is that by chance you have a byte in memory right after the last element of your array which cannot be interpreted as part of the representation of a floating-point number, which is why your implementation of std::atof stops. But that's something that cannot be relied upon.
You should fix your program this way:
char c[] = {'0', '.', '5', '\0'};
// ^^^^
No, atof does not work with non-null terminated arrays: it stops whenever it discovers zero after the end of the array that you pass in. Passing an array without termination is undefined behavior, because it leads the function to read past the end of the array. In your example, the function has likely accessed bytes that you have allocated to f (although there is no certainty there, because f does not need to follow c[] in memory).
char c[] = {'0','.','5'};
char d[] = {'6','7','8'};
float f = atof(c); // << Undefined behavior!!!
float g = atof(d); // << Undefined behavior!!!
cout << f*10;
The above prints 5.678, pointing out the fact that a read past the end of the array has been made.
No... atof() requires a null terminated string.
If you have a string you need to convert that is not null terminated, you could try copying it into a target buffer based on the value of each char being a valid digit. Something to the effect of...
char buff[64] = { 0 };
for( int i = 0; i < sizeof( buff )-1; i++ )
{
char input = input_string[i];
if( isdigit( input ) || input == '-' || input == '.' )
buff[i] = input;
else
break;
}
double result = atof( buff );
From the description of the atof() function on MSDN (probably applies to other compilers) :
The function stops reading the input string at the first character that it cannot recognize as part of a number. This character may be the null character ('\0' or L'\0') terminating the string.
It must either be 0 terminated or the text must contain characters that do not belong to the number.
std::string already terminate a string with NULL!
So why not
std::string number = "7.6";
double temp = ::atof(number.c_str());
You can also do it with the stringstream or boost::lexical_cast
http://www.boost.org/doc/libs/1_53_0/doc/html/boost_lexical_cast.html
http://www.cplusplus.com/reference/sstream/stringstream/
Since C++11, we have std::stof. By replacing atof with std::stof, it would be easier to handle.
I made a handy wrapper if you always pass a known size of char array.
Live Demo
#include <fmt/core.h>
#include <type_traits>
#include <iostream>
// SFINAE fallback
template<typename T, typename =
std::enable_if< std::is_pointer<T>::value >
>
float charArrayToFloat(const T arr){ // Fall back for user friendly compiler errors
static_assert(false == std::is_pointer<T>::value, "`charArrayToFloat()` dosen't allow conversion from pointer!");
return -1;
}
// Valid for both null or non-null-terminated char array
template<size_t sz>
float charArrayToFloat(const char(&arr)[sz]){
// It doesn't matter whether it's null terminated or not
std::string str(arr, sz);
return std::stof(str);
}
int main() {
char number[4] = {'0','.','4','2'};
float ret = charArrayToFloat(number);
fmt::print("The answer is {}. ", ret);
return 0;
}
Output: The answer is 0.42.
Does atof work with non-null terminated character arrays too?
No, this function expects a pointer to a null terminated string. Failing to do so, say for example by passing a pointer to a non-null terminated string(or a non-null terminated character array) is undefined behavior.
Undefined behavior means anything1 can happen including but not limited to the program giving your expected output. But never rely(or make conclusions based) on the output of a program that has undefined behavior.
So the output that you're seeing(maybe seeing) is a result of undefined behavior. And as i said don't rely on the output of a program that has UB. The program may just crash.
So the first step to make the program correct would be to remove UB. Then and only then you can start reasoning about the output of the program.
1For a more technically accurate definition of undefined behavior see this where it is mentioned that: there are no restrictions on the behavior of the program.

C++ atoi with unsigned char

I got an unsigned char with the value of 1, I need to put this in a string like "1".
But if I try to put this directly into a stringstream it will get the value of char(1) and I need it to be "1".
I know that if I can get this into the function atoi it will return the "1" value.
But I tried to cast it to char and put it in the atoi function, but it throws an exception.
Already tried to put it in a string and them cast c_str() into atoi function, but without success yet.
If someone can help me I'll apreciate.
Simply cast a char to an int before inserting it into the std::stringstream:
ss << static_cast<int>(c);
This will treat the value of the char not as a character but as a numerical value.
I believe you're confusing two functions here.
You wish to convert an integer to a string.
atoi (ascii to integer) however takes a string and parses it into an integer, making "123" into 123.
You are looking for the itoa function here, which has this prototype:
char * itoa ( int value, char * str, int base );
In your case, this would look like this:
char Buf[10];
itoa(123, Buf, 10);
printf("%s", Buf); //Outputs 123
Please remember though, that itoa is not part of the standard, even though it is supported by some compilers. For a more standard-compliant version use:
sprintf(Buf, "%d", 123);
Of course all of this is plain C, but any C++ compiler will work with this all the same.
To convert a numeric value in the range [0,9] to the corresponding character, just add '0' to it. That's guaranteed to work on all systems.

Different results using atoi

Could someone explain why those calls are not returning the same expected result?
unsigned int GetDigit(const string& s, unsigned int pos)
{
// Works as intended
char c = s[pos];
return atoi(&c);
// doesn't give expected results
return atoi(&s[pos]);
return atoi(&static_cast<char>(s[pos]));
return atoi(&char(s[pos]));
}
Remark: I'm not looking for the best way to convert a char to an int.
None of your attempts are correct, including the "works as intended" one (it just happened to work by accident). For starters, atoi() requires a NUL-terminated string, which you are not providing.
How about the following:
unsigned int GetDigit(const string& s, unsigned int pos)
{
return s[pos] - '0';
}
This assumes that you know that s[pos] is a valid decimal digit. If you don't, some error checking is in order.
What you are doing is use a std::string, get one character from its internal representation and feed a pointer to it into atoi, which expects a const char* that points to a NULL-terminated string. A std::string is not guaranteed to store characters so that there is a terminating zero, it's just luck that your C++ implementation seems to do this.
The correct way would be to ask std::string for a zero terminated version of it's contents using s.c_str(), then call atoi using a pointer to it.
Your code contains another problem, you are casting the result of atoi to an unsigned int, while atoi returns a signed int. What if your string is "-123"?
Since int atoi(const char* s) accepts a pointer to a field of characters, your last three uses return a number corresponding to the consecutive digits beginning with &s[pos], e.g. it can give 123 for a string like "123", starting at position 0. Since the data inside a std::string are not required to be null-terminated, the answer can be anything else on some implementation, i.e. undefined behaviour.
Your "working" approach also uses undefined behaviour.
It's different from the other attempts since it copies the value of s[pos]to another location.
It seems to work only as long as the adjacent byte in memory next to character c accidentally happens to be a zero or a non-digit character, which is not guaranteed. So follow the advice given by #aix.
To make it work really you could do the following:
char c[2] = { s[pos], '\0' };
return atoi(c);
if you want to access the data as a C string - use s.c_str(), and then pass it to atoi.
atoi expects a C-style string, std::string is a C++ class with different behavior and characteristics. For starters - it doesn't have to be NULL terminated.
atoi takes pointer to char for it's argument. In the first try when you are using the char c it takes pointer to only one character hence you get the answer you want. However in the other attempts what you get is pointer to a char which has happened to be beginning of a string of chars, therefore I assume what you are getting after atoi in the later attempts is a number converted from the chars in positions pos, pos+1, pos+2 and up to the end of the s string.
If you really want to convert just a single char in the string at the position (as opposed to a substring starting at that position and ending at the end of the string), you can do it these ways:
int GetDigit(const string& s, const size_t& pos) {
return atoi(string(1, s[pos]).c_str());
}
int GetDigit2(const string& s, const size_t& pos) {
const char n[2] = {s[pos], '\0'};
return atoi(n);
}
for example.

How to convert char* to unsigned short in C++

I have a char* name which is a string representation of the short I want, such as "15" and need to output this as unsigned short unitId to a binary file. This cast must also be cross-platform compatible.
Is this the correct cast: unitId = unsigned short(temp);
Please note that I am at an beginner level in understanding binary.
I assume that your char* name contains a string representation of the short that you want, i.e. "15".
Do not cast a char* directly to a non-pointer type. Casts in C don't actually change the data at all (with a few exceptions)--they just inform the compiler that you want to treat one type into another type. If you cast a char* to an unsigned short, you'll be taking the value of the pointer (which has nothing to do with the contents), chopping off everything that doesn't fit into a short, and then throwing away the rest. This is absolutely not what you want.
Instead use the std::strtoul function, which parses a string and gives you back the equivalent number:
unsigned short number = (unsigned short) strtoul(name, NULL, 0);
(You still need to use a cast, because strtoul returns an unsigned long. This cast is between two different integer types, however, and so is valid. The worst that can happen is that the number inside name is too big to fit into a short--a situation that you can check for elsewhere.)
#include <boost/lexical_cast.hpp>
unitId = boost::lexical_cast<unsigned short>(temp);
To convert a string to binary in C++ you can use stringstream.
#include <sstream>
. . .
int somefunction()
{
unsigned short num;
char *name = "123";
std::stringstream ss(name);
ss >> num;
if (ss.fail() == false)
{
// You can write out the binary value of num. Since you mention
// cross platform in your question, be sure to enforce a byte order.
}
}
that cast will give you (a truncated) integer version of the pointer, assuming temp is also a char*. This is almost certainly not what you want (and the syntax is wrong too).
Take a look at the function atoi, it may be what you need, e.g. unitId = (unsigned short)(atoi(temp));
Note that this assumes that (a) temp is pointing to a string of digits and (b) the digits represent a number that can fit into an unsigned short
Is the pointer name the id, or the string of chars pointed to by name? That is if name contains "1234", do you need to output 1234 to the file? I will assume this is the case, since the other case, which you would do with unitId = unsigned short(name), is certainly wrong.
What you want then is the strtoul() function.
char * endp
unitId = (unsigned short)strtoul(name, &endp, 0);
if (endp == name) {
/* The conversion failed. The string pointed to by name does not look like a number. */
}
Be careful about writing binary values to a file; the result of doing the obvious thing may work now but will likely not be portable.
If you have a string (char* in C) representation of a number you must use the appropriate function to convert that string to the numeric value it represents.
There are several functions for doing this. They are documented here:
http://www.cplusplus.com/reference/clibrary/cstdlib