Using sizeof to append a character to the end of a string form user
Asked Answered
B

4

6

I am trying to use the following code to append a right parenthesis to the end of a string took from the user

int main( void )
{
    char charArray[ 20 ];

    fgets( charArray, 20, stdin );

    charArray[ sizeof( charArray ) / sizeof( charArray[ 0 ] ) ] = ')';

    printf( "%s", charArray );
}

but if I enter: 4+5 the output is just: 4+5 rather then: 4+5)

I tried even the following variant, just in case

int main( void )
{
    char charArray[ 20 ];
    int n;

    fgets( charArray, 20, stdin );

    n = sizeof( charArray ) / sizeof( charArray[ 0 ] );

    charArray[ n ] = ')';

    printf( "%s", charArray );
}

but it doesn't work. So I obviated the need for a sizeof by doing

int main( void )
{
    char charArray[ 20 ];
    int i = 0;

    do{
        scanf( "%c", &charArray[ i ] );
        ++i;
    } while( charArray[ i - 1 ] != '\n' );

    charArray[ i - 1 ] = ')';

    printf( "%s", charArray );
}

However I could need sizeof in the future so I would like to know what I am doing wrong. Also the use of fgets and sizeof seems more direct and concise than that of scanf and do while with the mysterious [ i - 1 ] subscript. Just enlighten me about the sizeof approach please: what's wrong with my piece of code?

Buckling answered 31/8, 2020 at 10:12 Comment(2)
charArray[ sizeof( charArray ) / sizeof( charArray[ 0 ] ) ] = ')'; is by definition writing out of bounds of the array.Flotilla
You're manipulating strings, what about the use of strlen?Vasques
F
6

First, the size of char is always 1, it's defined in the specification.

Second, the size of an array is the size of the array itself, in "bytes".

That means

sizeof( charArray ) / sizeof( charArray[ 0 ] )

is equal to

20 / 1

which is equal to 20.

That is, you will always write to the twenty-first element of the array, which is out of bounds and lead to undefined behavior.

If you want to append a character to the string either use strcat to append the characters as a string (making sure you don't write out of bounds):

// Check to make sure there's space in the array, the -1 is for the terminator
if (strlen(charArray) < (sizeof charArray - 1))
{
    strcat(charArray, ")");
}

Or use strlen to get the position of the string null-terminator and overwrite it with your char (but remember to add a new null-terminator, as well as check so you don't go out of bounds):

// Get the index of the current null-terminator in the string
size_t terminator_index = strlen(charArray);

// Check to make sure there's space in the array, the -1 is for the terminator
if (terminator_index < (sizeof charArray - 1))
{
    // Can append new character
    charArray[terminator_index] = ')';

    // Add terminator
    charArray[terminator_index + 1] = '\0';
}
Ferrite answered 31/8, 2020 at 10:20 Comment(2)
Thank you so much for the explanation and the possibilities opened. So it appears that even the do while approach was wrong, since I overwrite the null-terminator. I tought that the terminator was fixed in the last subscript of the array. Also I recived 3 answers within a cople of minutes, I would like to thank you all! This site is a gold mine for programmers.Buckling
actually the do while is fine, i was overwriting the new lineBuckling
S
4

What you're doing wrong here is not knowing the difference between an array and a string. You have declared an array with size 20. That size will never change.

What you are looking for is the length of the string, and you get that with strlen(charArray).

A string is a sequence of printable characters terminated with the \0 character. The length of a string is the number of characters before the terminator.

This code will print 5, 20:

char arr[20] = "Hello";
printf("%zu, %zu\n", strlen(arr), sizeof(arr));

Note that this will print 3:

char arr[20] = "Hello";
printf("%zu\n", strlen(&arr[2]));

Also note that you can do things like this:

char arr[20] = "Hello\0World!\n";
printf("%zu, %zu\n", 
       strlen(&arr[0]), // Hello
       strlen(arr[strlen(&arr[0]) + 1]) // World!\n
);

This will print 5, 7. The second has a length of seven because the newline also counts. Also note that when you initialize an array, a '\0' is always appended, unless the size of the array is to small. This will declare a string without terminator:

char no_terminator[5] = "Hello";

Avoid that. It could cause you a lot of problems.

So in your case, you could do like this:

fgets( charArray, 20, stdin );

// Standard way of removing newline from a string. It's a very useful
// trick since fgets saves the newline in the string.
charArray[strcspn(charArray, "\n")] = '\0';

size_t len = strlen(charArray);
charArray[len] = ')';
charArray[len+1] = '\0';

But be careful so that len+1 never exceeds 20. If it does, you're invoking undefined behavior, which can be a real mess to debug. Also, it is wise to check if fgets returned NULL. If that's the case, you should handle the error.

If you handle this kind of input a lot, I could recommend this wrapper:

void my_readline(char *str, int n)
{
    if(fgets(str, n, stdin) == NULL) {
        perror("Error reading string");
        exit(EXIT_FAILURE);
    }
    str[strcspn(charArray, "\n")] = '\0';
}

Of course, you can customize the error handling in whatever way you want.

Swayne answered 31/8, 2020 at 10:20 Comment(0)
G
2

The expression sizeof(charArray) is twenty, and has nothing to do with the string that you have stored in it (unless that string is larger than twenty bytes, of course).

If you want to get the index of the \0 at the end of the string, you need to use strlen rather than sizeof.

For example, here's a way to do this safely:

#include <stdio.h>
#include <string.h>

int main( void ) {
    // Allow extra space for ')' and use actual size for input.

    char charArray[21];
    if (fgets(charArray, sizeof(charArray) - 1, stdin) == NULL) {
        fprintf(stderr, "Some problem occurred on input\n");
        return 1;
    }

    // Ensure terminated with newline.

    size_t len = strlen(charArray);
    if (len < 1 || charArray[len - 1] != '\n') {
        fprintf(stderr, "Input is not terminated with newline\n");
        return 1;
    }

    // Just replace newline with ')' character.

    charArray[len - 1] = ')';
    printf("%s\n", charArray);
}

By the way, if you're looking for a bullet-proof user input function in standard C, you'd be well advised to check out this one. I've used this for many years without problem.

Genseric answered 31/8, 2020 at 10:19 Comment(0)
Y
2

sizeof( char ) is always equal to 1. So this expression

sizeof( charArray ) / sizeof( charArray[ 0 ] )

is equivalent to the expression

sizeof( charArray )

because sizeof( charArray[ 0 ] ) is sizeof( char ) and as a result it yields the value of the size of the whole array that in the case of this declaration

char charArray[ 20 ];

is equal to 20.

It means that for example this statement

charArray[ sizeof( charArray ) / sizeof( charArray[ 0 ] ) ] = ')';

tries to write to the memory outside the array.

The function fgets appends the new line character '\n' provided that the user entered a string less than the specified size of the character array.

So what you need is to substitute the new line character for the character ')'.

You can do this easily using the standard C function strcspn the following way

size_t n = strcspn( charArray, "\n" );
if ( n + 1 < sizeof( charArray ) ) charArray[n] = ')';

In the condition of the if statement there is used the expression n + 1 to take into account the case when the array does not contain the new line character '\n' (the user entered more characters than the specified size). In this case n will yield the position of the terminating zero character '\0' that is less than the array size.

But we may not substitute it for the character ')' because otherwise the array will not contain a string. So this condition n + 1 < sizeof( charArray ) guarantees that the string contains the new line character '\n'.

Yorkshire answered 31/8, 2020 at 10:41 Comment(1)
In my textbook there is an entire chapter about string manipulation with fgets, strcspn and many other functions. I tought I couldn't remember them all and so I decided to read the chapter superficially to skip to the next, with the intention to use the book as a reference any time I needed to handle a string. It was indeed a bad decision. Thank you for showing me the value of strcspn.Buckling

© 2022 - 2024 — McMap. All rights reserved.