0

I have a really dumb question that I'm not able to get past.

The goal is to take in a string given by a user, and split it up by spaces and then putting that into an array.

This is my current code so far

#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define BUFFERSIZE 256
#define PROMPT "myShell >> "
#define PROMPTSIZE sizeof(PROMPT)

int main(int argc, char **argv) {


    //execvp() to locate executable

    char *buffer;
    size_t bufferSize = BUFFERSIZE;
    size_t inputSize;

    char *tokens;
    char myargv[BUFFERSIZE];

    buffer = (char *) malloc(bufferSize * sizeof(char));
    tokens = (char *) malloc(bufferSize * sizeof(char));


    while (1) {
        printf(PROMPT);
        inputSize = (size_t) getline(&buffer, &bufferSize, stdin);
        if (inputSize == 18446744073709551615) {
            break;
        }

        int i = 0;
        tokens = strtok(buffer, " ");
        while (tokens != NULL) {
            myargv[i] = (char) tokens;
            printf("%c\n", myargv[i]);
            tokens = strtok(NULL, " ");
            i = i + 1;
        }


    }

}

When I try to compile this, I get the warning,

warning: cast from pointer to integer of different size [-Wpointer-to-int-cast] myargv[i] = (char) tokens;

Not really sure exactly what I'm doing wrong.

Thanks!

  • maybe you want a `char *myargv[BUFFERSIZE];`. – Swordfish Oct 18 '18 at 04:11
  • I suspect you would exhaust your memory long before `inputSize == 18446744073709551615`. Note `getline` returns type `ssize_t` not `size_t` for this purpose. – David C. Rankin Oct 18 '18 at 04:13
  • @Swordfish that just gave me a coredump. – getlineHelp Oct 18 '18 at 04:15
  • @DavidC.Rankin that's just to see if Ctrl+D was pressed – getlineHelp Oct 18 '18 at 04:15
  • Sure, the simple and proper test is to make `inputSize` type `ssize_t` and then compare against `-1`. – David C. Rankin Oct 18 '18 at 04:16
  • 1. `sizeof(char)` is always 1. 2. Why do you inconsistently declare `myargv` as a static array, but `buffer` of exactly the same size as purpose - as a dynamic array? 3. The memory allocated for `tokens` is never use at all. – DYZ Oct 18 '18 at 04:21
  • @getlineHelp if you don't change `myargv[i] = (char) tokens; printf("%c\n", myargv[i]);` accordingly to `myargv[i] = tokens; printf("%s\n", myargv[i]);`, yes, it will. – Swordfish Oct 18 '18 at 04:54
  • Possible duplicate of [undefined reference to \`getline' in c](https://stackoverflow.com/questions/13112784/undefined-reference-to-getline-in-c) – DanMossa Oct 21 '18 at 00:34

2 Answers2

2

While it is not 100% clear what all you are trying to accomplish with your code, your use of the multiple pointers is a bit awkward.

The first thing that should be sounding ALARM BELLS for you is your need to explicitly cast to (char). If you ever find yourself trying to cast to get around compiler warnings or error -- STOP -- you are doing something wrong.

If your goal is to provide up to BUFFERSIZE arguments to execvp (or the like), then you simply need to declare myargv as an array of pointers to char, e.g.

    char *myargv[BUFFERSIZE] = {NULL};  /* array of pointers - init NULL */

Each of the pointers returned by strtok can be used as the argument array for execvp and if you initialize the array to all NULL pointers and fill no more than BUFFERSIZE - 1, you ensure you always provide an array of arguments to execvp and provide the required sentinel NULL after the final argument.

You can declare your delimiters for strtok any way you like, but since you are properly defining constants with #define, there is no reason not to add a constant for your strtok delimiters as well, e.g.

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

#define BUFFERSIZE 256
#define PROMPT "myShell >> "
#define DELIM " \n"

If you are not using argc or argv in your code, then the proper declaration for main() is:

int main (void) {

(see: C11 Standard §5.1.2.2.1 Program startup p1 (draft n1570). See also: See What should main() return in C and C++?)

If you are only reading the line and tokenizing the line to use with execvp, then declaring and initializing your variables within the loop scope ensures they are properly re-initialized each iteration, e.g.

    while (1) {
        size_t ndx = 0,             /* line index */
            n = 0;                  /* line alloc size (0, getline decides) */
        ssize_t nchr = 0;           /* return (chars read by getline) */
        char *line = NULL,          /* buffer to read each line */
            *myargv[BUFFERSIZE] = {NULL};  /* array of pointers - init NULL */

By declaring your inputSize, my nchr above as ssize_t (the proper return type for POSIX getline), you can simplify your test for EOF, e.g.

        fputs (PROMPT, stdout);
        if ((nchr = getline (&line, &n, stdin)) == -1) {
            putchar ('\n');         /* tidy up with newline */
            break;
        }

All that remains is tokenizing line and assigning the pointers to myargv at the proper index (ndx). You can use a while loop, but a for provides a convenient way to tokenize with strtok, e.g.

        for (char *p = strtok (line, DELIM); p; p = strtok (NULL, DELIM)) {
            myargv[ndx] = p;    /* points within line, duplicate as req'd */
            printf ("token: %s\n", myargv[ndx++]);
            if (ndx == BUFFERSIZE - 1)  /* preserve sentinel NULL */
                break;
        }
        /* call to execvp, etc. here */

(Note: by simply assigning the pointer to token to myargv[ndx], myargv[ndx] points to the location of the string within line. You must make use of the pointers while line remains in scope. Otherwise, you need to allocate memory for each token, assign the starting address for the new block of memory to myargv[ndx] and copy the token to the new block of memory. (either malloc and strcpy, or strdup if you have it))

Lastly, do not forget, getline allocates, so do not forget to free() the memory allocated when you are done with it, e.g.

        free (line);    /* don't forget to free memory allocated by getline */
    }

Putting it altogether, you could handle tokenizing your line with something similar to:

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

#define BUFFERSIZE 256
#define PROMPT "myShell >> "
#define DELIM " \n"

int main (void) {

    while (1) {
        size_t ndx = 0,             /* line index */
            n = 0;                  /* line alloc size (0, getline decides) */
        ssize_t nchr = 0;           /* return (chars read by getline) */
        char *line = NULL,          /* buffer to read each line */
            *myargv[BUFFERSIZE] = {NULL};  /* array of pointers - init NULL */

        fputs (PROMPT, stdout);
        if ((nchr = getline (&line, &n, stdin)) == -1) {
            putchar ('\n');         /* tidy up with newline */
            break;
        }
        for (char *p = strtok (line, DELIM); p; p = strtok (NULL, DELIM)) {
            myargv[ndx] = p;    /* points within line, duplicate as req'd */
            printf ("token: %s\n", myargv[ndx++]);
            if (ndx == BUFFERSIZE - 1)  /* preserve sentinel NULL */
                break;
        }
        /* call to execvp, etc. here */

        free (line);    /* don't forget to free memory allocated by getline */
    }

    return 0;
}

Example Use/Output

$ ./bin/getlinestrtok
myShell >> my dog has fleas
token: my
token: dog
token: has
token: fleas
myShell >> my cat has none
token: my
token: cat
token: has
token: none
myShell >> happy cat
token: happy
token: cat
myShell >>

Look things over and let me know if you have further questions.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
0

from strtok

char *strtok(char *str, const char *delim);

the return type is char * on the other hand in your assignment

myargv[i] = (char) tokens;

you are doing a typecast of char * to char I'm sure that's not what you want to do

may be something line this

change myargv to hold array of tokens

char myargv[MAX_TOKENS][BUFFERSIZE];

and in while loop instead of assignment myargv[i] = (char) tokens use strcpy

        strcpy(myargv[i], tokens);
        printf("%s\n", myargv[i]);

let me try and explain why your original program is misbehaving

char myargv[BUFFERSIZE];

here myargv is assigned a memory of BUFFERSIZE vitz 256 as in

+---+---+---+---+---+---+---+---+....---+---+
|   |   |   |   |   |   |   |   |   |   |   | 
+---+---+---+---+---+---+---+---+---+---+---+
  0   1   2   ..                          255

each block is of size sizeof(char) or 1 byte

in while loop here

myargv[i] = (char) tokens;

you got a char * which is essentially a 4 byte number, if you really go to that address and see what is there byte by byte you should have seen the first token. however you are now trying to put that 4 byte address in to 1 byte indexed location leading to truncation and assignment.

then comes the printf

printf("%c\n", myargv[i]);

now in based on what happened in the previous step, myargv[i] now contains a stripped version of an address, which is just a number "%c\n" format specifier tries to convert that into the corresponding ascii and print giving a garbage.

I'd suggest read somethings on 2d-arrays, array of strings, char **

asio_guy
  • 3,667
  • 2
  • 19
  • 35
  • That's a good point, but when I take away the `(char)` I just get random characters printed back. It works when I added the [MAX_TOKENS] although I'm not super sure why – getlineHelp Oct 18 '18 at 04:16
  • not just `MAX_TOKENS` but also you should have changed `%c` to `%s` – asio_guy Oct 18 '18 at 04:29