简体   繁体   中英

C program segfaulting with strtok

#include <stdio.h>
#include <unistd.h>

int main(void)
{
    int n_of_words = 0;
    #define MAX_STR_SZ 256

    // asking for user input
    char string[50];
    printf("\nPlease input a string of text.\n\n");
    fgets(string, MAX_STR_SZ, stdin);

    char * words[n_of_words]; 

    // extracting the first word
    words[n_of_words] = strtok(string, " ");
    printf("\n%i  %s\n", n_of_words, words[n_of_words]);

    // looping through the string to extract all other words
    while( words[n_of_words] != NULL )
    {
        n_of_words ++;
        words[n_of_words] = strtok(NULL, " ");
        printf("\n%i  %s\n", n_of_words, words[n_of_words]);
    }
    sleep(10);
    return 0;
}

I'm very new to programming, but I was trying to write a function to extract words from a user inputted string and save them in an array for later use in the program. I added the 2 printf lines of code to see if it was working properly. I always get a segmentation fault error after the second iteration of the while loop. Also, somehow this problem didn't present itself when I compiled the same code on the CS50 ide (Cloud9), but it happens in any other case.

Few issues which can be resolved to prevent segmenatation fault :

  1. No string.h header in the source code for strtok function
 #include <stdio.h> #include <unistd.h>
  1. Macros are generally declared in the top of the source code and not inside any function

#define MAX_STR_SZ 256

  1. The char string array is of length 50 but the fgets is allowing 256 and can lead to bufferoverflow.
 char string[50]; printf("\nPlease input a string of text.\n\n"); fgets(string, MAX_STR_SZ, stdin);
  1. The value of the variable n_of_words is 0 . So, the declaration

char * words[n_of_words];

Will not create an array of the desired length.

  1. The root cause of your question lies here :
while( words[n_of_words] != NULL )
{
     n_of_words ++;
     words[n_of_words] = strtok(NULL, " ");
     printf("\n%i  %s\n", n_of_words, words[n_of_words]);
}

You are accessing a memory location which was never declared,

 n_of_words ++; words[n_of_words] = strtok(NULL, " "); //words[1] or any index was never declared.

Every C program gets for free a list of the command line parameters, in general declared as int main(int argc, char* argv[]); or int main(int argc, char** argv);

This is precisely what you are trying to replicate with int n_of_words and char* words[n_of_words];

But you are doing it the wrong way.

A first note on this 3 lines from your code:

#define MAX_STR_SZ 256

char string[50];

fgets(string, MAX_STR_SZ, stdin);

You are setting 256 as the limit for fgets() to read, but you have only 50 chars in string. Many times it will work in this case, since you are reading from the keyboard and many of us would not key more than a few words in, but you have a problem. Change the limits.

strtok() is probably not the best one to choose here. A single loop using scanf() could read many lines and break all of then in words skipping over the newlines and such, and you may find it easier to code.

Anyway, back to your code: since you do not know in advance the number of words, you can estimate a limit or allocate memory for the strings one by one, or even in blocks. But

you need to allocate memory for the strings you will have a SegFault at the moment you try to write in the words[] array.

I changed a minimum of your code so you can see an example, and I fixed the number of strings in a #define similar of what you have written so far.

A simple way to go is declare --- as C does in main() --- words[] as char** and allocate memory for them as soon as you know you have at least one string to record.

But then you need to note that you will have just the pointers. They are still pointing to nothing.

As soon as you have a string to load you need to allocate memory for it, plus 1 byte for the terminating '\0' , and then copying the string and saving the address in the corresponding pointer in the words[] array.

See the code.

#define MAX_STR_SZ 256
#define MAX_N_OF_STRINGS 30
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// https://stackoverflow.com/questions/63343800/
// c-program-segfaulting-with-strtok

int main(int argc, char** argv)
{
    int n_of_words = 0;
    int max_n_of_words = MAX_N_OF_STRINGS;
    char** words;

    // asking for user input
    char string[MAX_STR_SZ];
    printf("\nPlease input a string of text: ");
    fgets(string, MAX_STR_SZ, stdin);
    string[strlen(string) - 1] = 0; // drops the final '\n'
    printf("full string was '%s'\n", string);
    if (strlen(string) == 0) return -1; // no input

    // we have at least one byte

    // before anything build words[]
    words = (char**)malloc(max_n_of_words * sizeof(char*));
    // now words[] points to an array of pointers to char

    // extracting the first word
    char* a_word = strtok(string, " ");

    // looping through the string to extract all other words
    do
    {
        printf("\n%i  %s\n", 1+n_of_words, a_word);
        words[n_of_words] = malloc(1 + sizeof(a_word));
        strcpy(words[n_of_words], a_word);
        n_of_words++;
        if (n_of_words >= MAX_N_OF_STRINGS) break;
        a_word = strtok(NULL, " ");
    }   while (a_word != NULL);

    printf("\n%d words at the end of the loop:\n\n", n_of_words);
    for (int i = 0; i < n_of_words; i += 1)
    {
        printf("%i  %s\n", 1 + n_of_words, words[i]);
        free(words[i]); // deletes words[i]
    };  // for()
    free(words); // deletes the array
    return 0;
};

As a result:

Please input a string of text: we have at least one byte
full string was 'we have at least one byte'

1  we

2  have

3  at

4  least

5  one

6  byte

6 words at the end of the loop:

1  we
2  have
3  at
4  least
5  one
6  byte

There are a few problems that could lead to a seg fault. First, I get warnings compiling your code:

../main.c: In function 'main':
../main.c:17:25: warning: implicit declaration of function 'strtok' [-Wimplicit-function-declaration]
     words[n_of_words] = strtok(string, " ");
                         ^~~~~~
../main.c:17:23: warning: assignment makes pointer from integer without a cast [-Wint-conversion]
     words[n_of_words] = strtok(string, " ");
                       ^
../main.c:24:27: warning: assignment makes pointer from integer without a cast [-Wint-conversion]
         words[n_of_words] = strtok(NULL, " ");

All of this is because you didn't include the proper header for strtok , namely string.h . This could potentially cause problems because the default return type is assumed to be int , which may not be large enough to hold a pointer.

Second, you are passing an incorrect size to fgets() . The size should be the size of the buffer for holding the result. If the buffer is overflowed, undefined behavior results.

Finally, the words array is declared with a size n_of_words , which is zero at that point. This results in a zero size array. Arrays in C do not automatically grow.

Here is your code with these issues fixed:

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

int main(void)
{
    int n_of_words = 0;
    #define MAX_STR_SZ 256

    // asking for user input
    char string[MAX_STR_SZ];  // <--- Use macro to define buffer size
    printf("\nPlease input a string of text.\n\n");
    fgets(string, sizeof string, stdin);

    char * words[MAX_STR_SZ]; // <--- Should never be more words than characters in the buffer

    // extracting the first word
    words[n_of_words] = strtok(string, " ");
    printf("\n%i  %s\n", n_of_words, words[n_of_words]);

    // looping through the string to extract all other words
    while( words[n_of_words] != NULL )
    {
        n_of_words ++;
        words[n_of_words] = strtok(NULL, " ");
        printf("\n%i  %s\n", n_of_words, words[n_of_words]);
    }
    sleep(10);
    return 0;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM