简体   繁体   中英

Why are the values in my dynamic 2D char array being overwritten?

I'm trying to build a process logger in C on Linux, but having trouble getting it right. I'd like it to have 3 colums: USER, PID, COMMAND. I'm using the output of ps aux and trying to dynamically append it to an array. That is, for every line ps aux outputs, I want to add a row to my array.

This is my code. (To keep the output short, I only grep for sublime. But this could be anything.)

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

int main()
{
    char** processes = NULL;
    char* substr = NULL;
    int n_spaces = 0;
    int columns = 1;

    char line[1024];
    FILE *p;
    p = popen("ps -eo user,pid,command --sort %cpu | grep sublime", "r");
    if(!p)
    {
        fprintf(stderr, "Error");
        exit(1);
    }

    while(fgets(line, sizeof(line) - 1, p))
    {
        puts(line);
        substr = strtok(line, " ");
        while(substr != NULL)
        {
            processes = realloc(processes, sizeof(char*) * ++n_spaces);

            if(processes == NULL)
                exit(-1);

            processes[n_spaces - 1] = substr;

            // first column user, second PID, third all the rest
            if(columns < 2)//if user and PID are already in array, don't split anymore
            {
                substr = strtok(NULL, " ");
                columns++;
            }
            else
            {
                substr = strtok(NULL, "");
            }
        }
        columns = 1;
    }
    pclose(p);

    for(int i = 0; i < (n_spaces); i++)
        printf("processes[%d] = %s\n", i, processes[i]);

    free(processes);

    return 0;

}

The output of the for loop at the end looks like this.

processes[0] = user
processes[1] = 7194
processes[2] = /opt/sublime_text/plugin_host 27184

processes[3] = user
processes[4] = 7194
processes[5] = /opt/sublime_text/plugin_host 27184

processes[6] = user
processes[7] = 27194
processes[8] = /opt/sublime_text/plugin_host 27184

processes[9] = user
processes[10] = 27194
processes[11] = /opt/sublime_text/plugin_host 27184

But, from the puts(line) I get that the array should actually contain this:

user       5016 sh -c ps -eo user,pid,command --sort %cpu | grep sublime
user       5018 grep sublime
user      27184 /opt/sublime_text/sublime_text
user      27194 /opt/sublime_text/plugin_host 27184

So, apparently all the values are being overwritten and I can't figure out why... (Also, I don't get where the value 7194 in processes[0] = 7194 and processes[4] = 7194 comes from).

What am I doing wrong here? Is it somehow possible to make the output look like the output of puts(line) ?

Any help would be appreciated!

strtok returns a pointer into the string it processes. That string is, always, stored in the variable line . So all your pointers in the array point to the same place in memory.

As @Lundin wrote in a comment, there is no two-dimensional array here, just a one-dimensional array of pointers.

The return value of strtok is a pointer into the string that you tokenise. (The token has been made null-terminated by overwriting the first separator after it with '\\0' .)

When you read a new line with fgets , you overwrite the contents of this line and all tokens, not just the ones from the last parsing, point to the actual content of the line. (The pointers to the previous tokens remain valid, but the content at these locations changes.)

The are several ways to fix this.

  • You could make the tokens you save arrays of chars and strcpy the parsed contents.
  • You could duplicate the parsed tokens with (the non-standard) strdup , which allocates memory for the strings on the heap.
  • You could read an array of lines, so that the tokens are really unique.

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