[Solved] Run-time error storing pointer character from strtok after multiple calls


Remember that strtok modifies the buffer.

The caller to your functions would have to pass a temporary buffer that has been copied from the original before each call.

In other words, the call to ModifiedStringSize trashes inputString so that when you call ManipulateString, the updated value for inputString is [effectively] garbage.

The usual here is to parse a buffer only once and retain a char ** array that is built up from the strtok loop.


UPDATE:

Sorry, I’m still learning C and I’m not really that good with pointers. How exactly do I implement this?

There are basically two ways to do this:

  1. Caller passes a pointer to a fixed size char ** array that the subfunction fills in with tokens [for a given line]
  2. The subfunction uses malloc/realloc to create and maintain a dynamically increasing list of tokens. It returns a pointer to that array.

Option (1) is easier/simpler and is suitable for line-at-a-time processing. That is, all necessary processing can be done without having to combine multiple lines of input (e.g. this is probably suitable for your use case).

Option (2) is used if we have to read an entire multiline file and store all lines before doing any processing. Or, we can not predict how many tokens we’ll need before attempting to parse. It is the caller’s responsibility to call free on the array elements and the array pointer itself.

Note that I had to refactor your code a bit.


Here’s the first method:

// Inputs a string with a maximum of 100 characters
// tokenizes it and converts it to pig latin in a new string
#include <stdio.h>
#include <string.h>
#include <ctype.h>

#define SIZE        1000
#define DELIMS      " \t\n"

void piglatin(char *pig,const char *inp);
void pigall(char **toklist);

// parse_string -- parse a line into tokens
// RETURNS: actual count
int
parse_string(char *inputString,char **toklist,int tokmax)
{
    char *bp = inputString;
    char *cp;
    int tokcount = 0;

    // allow space for NULL terminator
    --tokmax;

    while (1) {
        cp = strtok(bp,DELIMS);
        bp = NULL;

        if (cp == NULL)
            break;

        if (tokcount >= tokmax)
            break;

        toklist[tokcount++] = cp;
    }

    // add end of list
    toklist[tokcount] = NULL;

    return tokcount;
}

int
main(void)
{
    // inputString is where
    // string entered by user
    // is stored
    char inputString[SIZE];

    char pig[SIZE];

    // some simple tests
    piglatin(pig,"pig");
    piglatin(pig,"smile");
    piglatin(pig,"omelet");

    // fgets reads up to 100
    // characters into inputString
    printf("Enter phrase: ");
    fflush(stdout);
    fgets(inputString, SIZE, stdin);

    // define the worst case number of tokens that are possible (e.g. each
    // token is one char)
    char *toklist[((SIZE + 1) / 2) + 1];

    // parse the string into tokens
    int tokcount = parse_string(inputString,toklist,
        sizeof(toklist) / sizeof(toklist[0]));
    printf("tokcount is %d\n",tokcount);

    // translate all tokens into pig latin
    pigall(toklist);

    return 0;
}

Here’s the second method:

// Inputs a string with a maximum of 100 characters
// tokenizes it and converts it to pig latin in a new string
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#define SIZE        1000
#define DELIMS      " \t\n"

void piglatin(char *pig,const char *inp);
void pigall(char **toklist);

// parse_file -- parse all tokens in a file
// RETURNS: token list
char **
parse_file(FILE *xfsrc,size_t *tokc)
{
    char *bp;
    char *cp;
    char buf[SIZE];
    size_t tokmax = 0;
    size_t tokcount = 0;
    char **toklist = NULL;

    while (1) {
        // get next line
        cp = fgets(buf,sizeof(buf),xfsrc);
        if (cp == NULL)
            break;

        bp = buf;
        while (1) {
            // get next token on line
            cp = strtok(bp,DELIMS);
            bp = NULL;
            if (cp == NULL)
                break;
            printf("DEBUG: '%s'\n",cp);

            // NOTE: using tokmax reduces the number of realloc calls
            if (tokcount >= tokmax) {
                tokmax += 100;
                toklist = realloc(toklist,sizeof(*toklist) * (tokmax + 1));
            }

            // NOTE: we _must_ use strdup to prevent the first and subsequent
            // lines from being jumbled together in the _same_ buffer
            toklist[tokcount++] = strdup(cp);

            // add null token to end of list (just like argv)
            toklist[tokcount] = NULL;
        }
    }

    // trim list to amount actually used
    if (toklist != NULL)
        toklist = realloc(toklist,sizeof(*toklist) * (tokcount + 1));

    // return number of tokens to caller
    *tokc = tokcount;

    return toklist;
}

// tokfree -- free the token list
char **
tokfree(char **toklist)
{

    for (char **tok = toklist;  *tok != NULL;  ++tok)
        free(*tok);

    free(toklist);

    toklist = NULL;

    return toklist;
}

int
main(void)
{

    // some simple tests
    char pig[SIZE];
    piglatin(pig,"pig");
    piglatin(pig,"smile");
    piglatin(pig,"omelet");

    // parse entire file
    size_t tokcount;
    char **toklist = parse_file(stdin,&tokcount);

    // translate all tokens into pig latin
    pigall(toklist);

    // free the list
    toklist = tokfree(toklist);

    return 0;
}

Spoiler Alert: Here’s the pig latin code I used to test the above with:

// piglatin.c -- convert words to pit latin

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

int
isvowel(int chr)
{

    chr = (unsigned char) chr;
    chr = tolower(chr);
    return (strchr("aeiou",chr) != NULL);
}

void
piglatin(char *pig,const char *inp)
{
    const char *rhs;
    char *lhs;
    char pre[100];

    *pig = 0;
    rhs = inp;

    do {
        if (isvowel(*rhs)) {
            strcat(pig,rhs);
            strcat(pig,"yay");
            break;
        }

        lhs = pre;
        for (;  *rhs != 0;  ++rhs) {
            if (isvowel(*rhs))
                break;
            *lhs++ = *rhs;
        }
        *lhs = 0;

        strcat(pig,rhs);
        strcat(pig,pre);
        strcat(pig,"ay");
    } while (0);

    printf("%s --> %s\n",inp,pig);
}

void
pigall(char **toklist)
{
    char pig[1000];

    for (char **tokp = toklist;  *tokp != NULL;  ++tokp)
        piglatin(pig,*tokp);
}

1

solved Run-time error storing pointer character from strtok after multiple calls