In the code below that reverses a string, I don't understand what is the purpose of the if statement. What case is this if statement trying to catch for?
Isn't this superfluous since you've already set char*end= str, and so the statement if(str) has to be true or your code would have already failed by this point?
void reverse(char* str)
{
char *end = str;
char temp;
if (str)
{
while (*end)
{
end++;
}
}
end--; //pulls back one for \0 character
while (str < end)
{
temp = *str;
*str++ = *end;
*end-- = temp;
}
}
It prevents dereferencing a NULL pointer, very good practice.
In case str == NULL, then *end will be undefined behavior, so checking that before dereferencing, is really a good thing to do, because ignoring such possibility will cause a bug that will be very hard to detect, except of course using a debugger, but still, why leave the need to track the problem, when you can avoid it at the expense of almost nothing, generally.
It would actually be better to do something like this
if (str == NULL)
return;
Isn't this superfluous since you've already set char*end= str, and so
the statement if(str) has to be true or your code would have already
failed by this point?
Actually, the line
char *end = str;
will not cause the "code to fail", even if a null pointer was passed as an argument. Then it would just assign NULL to end.
What case is this if statement trying to catch for?
Since we do not know if we were passed a NULL pointer yet, we should check before dereferencing it. That is what the if statement is for.
This code would work correctly:
void reverse(char* str)
{
if (str != NULL)
{
char *end = str;
while (*end != '\0')
end++;
end--; //pulls back one for \0 character
while (str < end)
{
char temp = *str;
*str++ = *end;
*end-- = temp;
}
}
}
This code would also work:
void reverse(char* str)
{
if (str == NULL)
return;
char *end = str; // Assumes C99 or later
while (*end != '\0')
end++;
end--; //pulls back one for \0 character
while (str < end)
{
char temp = *str;
*str++ = *end;
*end-- = temp;
}
}
Both ensure that you never mess with a null pointer. Decrementing a null pointer as in the original code would lead to catastrophe (or, at least, undefined behaviour). Don't risk it.
You can also use strlen() — carefully ensuring you don't run into problems with an empty string:
void reverse(char* str)
{
if (str == NULL || *str == '\0')
return;
char *end = str + strlen(str) - 1;
while (str < end)
{
char temp = *str;
*str++ = *end;
*end-- = temp;
}
}
Related
I have 3 problems but I think they're essentially the same.
I have 3 functions that are doing something on the String they get as parameter, but when I try to return the result, I get a segmentation fault every time and I can't understand why.
/* the function gets a string, and return the string without blank spaces (is exists) in the end of the string */
char *trimRightSpaces(char *str, unsigned int len)
{
unsigned int i = len;
char *trimStr = NULL;
if(str == NULL)
return NULL;
while(i > 0)
{
if(isspace((int)str[i]))
break;
i--;
}
strncpy(trimStr, str, i-1);
trimStr[i+1] = '\0';
return trimStr;
}
and this function gets labels from my program, that are just strings with ':' character in the end:
/* The function gets a pointer to a string, and returns the name of label without ':' if it's a valid label, NULL if not. */
char *isLabel(char *str)
{
int i;
char tempStr[80];
if(str == NULL)
return NULL;
if(strlen(str) > VALID_LABEL_LENGTH)
return NULL;
for(i = 0; i < strlen(str); i++)
{
if(i == 0)
{
/* if the first letter of the string is not small or capital letter, then it's not a valid label. */
if(!isalpha((int)str[0]))
return NULL;
}
if(i == (strlen(str) - 1))
{
if((int)str[i] != ':') /* check it the word ends with ':', otherwise it's not a valid label */
return NULL;
break;
}
if(!isalnum((int)str[i]))
return NULL;
}
strncpy(tempStr, str, strlen(str) - 1);
if(isLanguageWord(tempStr))
return NULL;
return str;
}
and:
/* the function get a string, and check if it's a valid string: a string inside " ". if it's valid, it returns the string without " ", and NULL otherwise. */
char *validString(char *str)
{
char temp[80];
char *temp2 = NULL;
int i;
if(str == NULL)
return NULL;
if(str[0] != '"' || str[strlen(str) - 1] != '"')
return NULL;
for(i = 1; i < strlen(str) - 1; i++)
{
temp[i] = str[i];
}
temp2 = temp;
return temp2;
}
in the function:
char *validString(char *str)
this statement:
temp2 = temp;
is setting the pointer to point to a local variable.
Then this statement:
return temp2;
is returning that pointer.
The problem is when the local variable 'goes out of scope' (which happens when the function exits.) that stack variable no longer is usable.
accessing the contents of that variable in the calling function is undefined behavior.
I.E. the function can be reduced to :
/* the function get a string, and check if it's a valid string: a string inside " ". if it's valid, it returns the string without " ", and NULL otherwise. */
char *validString(char *str)
{
if(str == NULL)
return NULL;
if(str[0] != '"' || str[strlen(str) - 1] != '"')
return NULL;
return strdup( str );
} // end function: validString
the function: trimRightSpaces() does not actually trim the trailing spaces AND has the same problem as the validString() function. Suggest:
/* the function gets a string, and return the string without blank spaces (is exists) in the end of the string */
char *trimRightSpaces( char *str )
{
if(str == NULL)
return NULL;
size_t i;
for( i = strlen( str ); i > 0 && ' ' == str[i]; i-- );
char *trimstr = strdup( str );
if( trimstr )
trimstr[i] = '\0'; // note: this may need to be [i+1]
return trimstr;
} // end function: trimRightSpaces
the function: islabel() has the same problem AND a few other problems. Suggest:
/* The function gets a pointer to a string, and returns the name of label without ':' if it's a valid label, NULL if not. */
char *isLabel(char *str)
{
if(str == NULL)
return NULL;
if(strlen(str) > VALID_LABEL_LENGTH)
return NULL;
if(isLanguageWord(str)) // note: this 'may' not work, but function not posted so don't know
return NULL;
if(str[strlen( str ) -1] != ':') /* check it the word ends with ':', otherwise it's not a valid label */
return NULL;
/* if the first letter of the string is not small or capital letter, then it's not a valid label. */
if(!isalpha((int)str[0]))
return NULL;
for( size_t i = strlen(str); i ; i-- )
{
if(!isalnum((int)str[i]))
return NULL;
}
return strdup( str );
} // end function: isLabel
There are various problems in your code.
For example, if you declate pointer to char
char *trimStr = NULL; // Points to NULL, cant dereference
char *trimStr; // Uninitialized ptr, cant dereference
then you cant dereference NULL or uninitialized pointer. Whats worse you are trying to copy data inside. What you have to do is allocate enough memory on heap, for example
char *trimStr = malloc(len);
Then you can copy len bytes into trimStr.
Second, you cant return pointer to local variable like you do in validString because after program counter returns back from function, the frame is destroyed and local variable is out of scope -> no more reachable. You have to return pointer to allocated memory on heap or pass an pointer to array to function, where you will fill this array.
Also i can see some logical problems like
if(isspace((int)str[i]))
Where you want to trim spaces but isspace returns non-zero if its space. So you have to negate the expression
if(!isspace((int)str[i]))
Here is the example of trimRightSpaces function.
char *strcat(char*dest, char*src) {
while (dest != '\0') {
*dest++;
}
while (src != '\0') {
*dest++ = *src++;
}
return dest;
}
I keep getting a segmentation fault on the line *dest++ = *src++. Any ideas on how to fix the problem?
Your code has 4 problems:
you are comparing pointers to the null character instead of comparing the character they point to. Since it will take incrementing the pointer an awful lot of times before it becomes 0, if at all, you are reading and/or writing beyond the end of the buffers, from/to invalid memory before this happens, hence the crash.
you do not null terminate the destination string.
you return the pointer to the end of the destination string instead of the original destination string. This might be a useful API, but you should use a different name for that.
the src pointer should be declared as const char * to conform to the standard declaration for this function and to allow passing pointers to constant strings as sources.
Here is a corrected version:
char *strcat(char *dest, const char *src) {
char *saved = dest;
while (*dest != '\0') {
dest++;
}
while ((*dest++ = *src++) != '\0') {
continue;
}
return saved;
}
Okay: the Kernighan way:
char *strcat(char *dest, char *src)
{
char *org = dest;
while(*dest++){;}
// at this moment, *dest MUST be pointing to '\0'
while(*dest++ = *src++){;}
// at this moment, *dest MUST be pointing to '\0' agian
return org;
}
Update (courtously #chqrlie):
char *strcat(char *dest, char *src)
{
char *org = dest;
for(; *dest; dest++) {;}
// at this moment, dest MUST be pointing to '\0'
while(*dest++ = *src++) {;}
// at this moment, dest points past the '\0', but who cares?
return org;
}
dest and source will never become '\0' if they aren't null to begin with (or maybe after a long time to be correct, but you'll probalby run out of memory long before that).
You should use:
while(*dest != '\0'){
dest++;
}
while(*src != '\0'){
*dest++ = *src++;
}
to check the values underneath the pointers.
There are some other problems too:
the resulting string is not null-terminated.
a pointer to the end of the string is returned.
As mentioned by others: src should be a const pointer too.
This should do it:
char *strcat(char *dest, const char *src)
{
char *start_pos = dest;
while(*dest != '\0')
dest++;
while(*src != '\0')
*dest++ = *src++;
*dest = '\0';
return start_pos;
}
Minor detail: I would give this funtion some other name than the standard used strcat().
please help me out , I'm trying to implement strchr and I still get Null when I run this code... what is it wrong with it?
char *ft_strchr(const char *str, int c)
{
int i;
char *temp;
i = 0;
while (str[i])
{
if (str[i] == c)
{
*temp = str[i];
return (temp);
}
else
return (NULL);
i++;
}
return (str);
}
char* ft_strchr(const char *str, int c){
size_t i;
char *temp;
i = 0;
while (str[i])
{
if (str[i] == c)
{
temp = &str[i];
return temp;
}
i++;
}
if(str[i]==c)
{
return &str[i];
}
return NULL;
// You need to return NULL after scanning whole line..
// Or it will send NULL checking after 1st character
}
strchr is supposed to return a pointer to the matching character in the string. You're returning a pointer, but it doesn't point into the string. You never initialized it, so it doesn't point anywhere.
Change
*temp = str[i];
to:
temp = &str[i];
the following code:
makes use of the fact the C passes by value rather than by reference
eliminates the code clutter,
uses a for() statement so the compiler handles all the loop details,
eliminates all the code clutter
Note: this kind of expression: *str evaluates to true, except when the char pointed at is '\0'.
So the below code walks through the passed in char string, terminating on either of two conditions.
1) a matching char is found in the string or
2) end of string is encountered.
The return statement returns NULL if end of string is encountered, else returns the address of where the matching char is first found in the string.
char *ft_strchr(const char *str, int c)
{
for( ; *str && (*str != c); str++ ) ;
return ( *str? str : NULL);
}
1.) Is it possible to reverse a string including the NULL character
(which means that “abcd” is represented as five characters, including the null character.)
2.) In my current implementation, that doesn't take 1.) into account,
I am getting segmentation error during swapping. ie while assigning: *str = *end;
void reverse(char *str)
{
char * end = str;
char tmp;
if (str)
{ // to handle null string
while (*end)
{ // find the end character
++end;
}
--end; // last meaningful element
while (str < end) // terminal condition: str and end meets in the middle
{ tmp = *str; // normal swap subroutine
*str = *end; // str advance one step
*end = tmp; // end back one step
str++;
end-- ;
}
}
return;
}
Your function is correct. It seems that the problem is that you are trying to reverse a string literal. You may not change string literals. They are immutable. Any attemp to change a string literal results in undefined behaviour of the program.
From the C Standard (6.4.5 String literals)
7 It is unspecified whether these arrays are distinct provided their
elements have the appropriate values. If the program attempts to
modify such an array, the behavior is undefined
Only take into account that it would be better to write
if ( *str )
instead of
if (str)
Or if you want to check that the poinetr is not NULL then
if ( str && *str )
In this case this decrement
--end;
will be valid even if the original string is empty.
Nevertheless the function itself could be defined the following way as it is shown in the demonstrative program
#include <stdio.h>
char * reverse( char *s )
{
char *last = s;
while ( *last ) ++last;
if ( last != s )
{
for ( char *first = s; first < --last; ++first )
{
char c = *first;
*first = *last;
*last = c;
}
}
return s;
}
int main( void )
{
char s[] = "Hello arshdeep kaur";
puts( s );
puts( reverse( s ) );
}
The program output is
Hello arshdeep kaur
ruak peedhsra olleH
Calling your code like this:
int main()
{
char a[]="abcd";
int i;
reverse(a);
for (i=0;i<5;i++) {
printf("a[%d]=%02x\n",i,a[i]);
}
}
Outputs this:
a[0]=64
a[1]=63
a[2]=62
a[3]=61
a[4]=00
So you're probably passing in a string literal (i.e. char *a="abcd";). These literals are commonly stored in a read-only section of memory, which is likely why it is core dumping.
That being said, doing a reversal including the null character is not of much practical usage when you're dealing with strings.
I'm quite certain you can. You just need the length of the string and be aware to test NUL.
Strings can and probably should be thought of as character arrays. In particular, trying to direction assign a string literal to an already initialized string is an invalid operation.
An Example:
Here is one way to reverse a string:
void reverse(char *str) {
// First calculate the length
unsigned int length = 0;
int i = 0;
for (; str[i] != '\0'; i++) {
++length;
}
++length;
// Allocate temporary storage
char tmp = malloc(length);
int x = 0;
// Loop through starting at the end and go backwards
// It is perfectly legal to change the characters themselves, just not the pointer
for (i = length - 1; i >= 0; i++, x++) {
if (str[i] != '\0') {
tmp[x] = str[i];
}
}
tmp[length - 1] = '\0';
// Reassign to the given argument
for (i = 0; i < length; i++) {
str[i] = tmp[i];
}
// Free memory
free(tmp);
}
Like the other answer stated, you're trying to do something that is left as undefined behavior in the standard.
You can try this :
void reverse(char *str) {
char *end = str;
char tmp;
if (str) {
while (*end){
++end;
}
--end;
while (str < end) {
tmp = *str;
*str++ = *end;
*end-- = tmp;
}
}
}
void r(char *str)
{
char *new = str;
while (*str != '\0') {
if (*str != ' ') *(new++) = *str;
str++;
}
*new = '\0';
}
I have this function but i don't understand the code after the if statement .I would be grateful if somebody could explain it to me.
This function is stripping spaces out of the passed in value str.
*(new++) = *str; means take the pointer to the string new and dereference it so that we can use it to store where in memeory it points. Then take the value that is pointed to by str and dereference it to the char it points at. Assign that char to where new is and then move the new pointer along to the next spot. Finally, always move the str pointer along with str++ if ithe value is a space or not.
May be it would be clearer to you if were wrote this way:
void r(char *str)
{
char *new = str;
int newStringIndex = 0;
for(int i=0; str[i] != '\0'; i++)
{
if (str[i] != ' ')
{
new[newStringIndex++] = str[i];
}
}
new[newStringIndex] = '\0';
}
Same code functionality, but using a for loop and array indexing instead of pointer math.
the function is removing spaces (in-place) - it copies everything but spaces to the same string as before.
so the if statement is saying: "if the character in str is not a space, then copy it to new" (which is in the same memory area as str, but will start to trail behind when str meets a space).
note how str is always incremented, but new is only incremented when a character is copied. so str scans the whole string, but the data are copied to earlier in the string because new doesn't update when there's a space.
then finally a terminating null is added at new so that the shortened version terminates correctly.
This version of the program (K&R style!) is shorter and does the same:
void r(char *str)
{
char *new;
for (new=str; *new = *str++; ) {
if (*new != ' ') new++;
}
}
while (*str != '\0') {
if (*str != ' ') *(new++) = *str;
str++;
}
is equivalent to:
while (*str != '\0') {
if (*str != ' ') {
*new = *str;
new++;
}
str++;
}