0

Description

I am trying to write a csv table, tablepath, and I need to include in it the name of the variables, which are in a text file, filepath. I am using a first function, read_par, to retrieve the names from filepath and a second function, store, to write in the table.

Problem

The table created is systematically missing the name of the first variable from the text file. The read_par function is functional and produces the expected output: a string containing the name of the variable, I also included it for context.

filepath

  • Here is the structure of the text file:

par1 0 1 0.5 par2 1 1 1 par3 0 1 1 par4 0 1 1 par5 0 1 1 par6 0 1 1

store

  • Here is the store function:

    int store(int par_num, float sim_num, float **tab_param, char *filepath, char *tablepath){
    
    int j;
    char *name = NULL;
    FILE* sim_table = NULL;
    sim_table = fopen(tablepath, "w");  
    
    // Start the first line in the table
    fprintf(sim_table,"simulation_number");
    
    for(j=1; j < par_num+1; j++){
    
        // If it is the last parameter -> create a new line
        if(j == par_num){
    
            name = read_name(j, filepath);
            fprintf(sim_table,",%s\n", name);
        }else{
            /* If it is not the last parameter -> continue writing on 
             * the same line */
            name = read_name(j, filepath);
            fprintf(sim_table,",%s", name);
        }
    }
    fclose(sim_table);
    return 0;
    }
    

read_name

  • Here is the read_name function:

    char *strtok(char *line, char *eof);    
    
    char *read_name(int par_id, char *filepath){
    
    char *par_name;
    int count = 0;
    
    FILE *file = fopen(filepath, "r");
    
    if ( file != NULL ){
    
        char line[256]; /* or other suitable maximum line size */
        while (fgets(line, sizeof line, file) != NULL){ /* read a line */
    
            if (count == (2*par_id)-2){
    
                // strtok removes the \n character from the string line
                strtok(line, "\n");
                par_name = line;
    
                fclose(file);
            }
            else{
                count++;
            }
        }
    }
    else
    {
        printf("\n\n ERROR IN FUNCTION READ_PAR\n\nTEXT FILE IS EMPTY\n\n");
    }
    return par_name;
    }
    

tablepath

The table I am obtaining looks like this:

┌─────────────────┬┬────┬────┬────┬────┬────┐
│simulation_number││par2│par3│par4│par5│par6│
└─────────────────┴┴────┴────┴────┴────┴────┘

With the par1 name missing, but all the other variable name successfully printed. I do not know where is the problem. Is it a problem in the for loop conditions or something to do with the par1 string itself?

Thanks for any help on this.

Goûtay
  • 49
  • 5
  • One problem is that `read_name` returns the address of `line`. `line` is a local variable which goes out of scope (ceases to exist) at the end of the function. – user3386109 May 17 '18 at 19:06
  • @user3386109 I do not understand your comment. The `read_name` function does indeed returns the address of a string, but I tested this part of the code and it is functional. The fact that the names `par2` to `par6` are present in the table proves it, but could the problem you are mentioning explain somehow why the first element, `par1` is not being written to the table? – Goûtay May 18 '18 at 16:32
  • Yup, the worst thing about [undefined behavior](https://stackoverflow.com/questions/2397984) in C is that sometimes the code appears to work, even when it is technically incorrect. And by technically incorrect, I mean that the code *will* break at some point. For example, if you add another function call between the call to `read_name` and the call to `fprintf`, there's a good chance that `name` will be corrupted, and won't print properly. – user3386109 May 18 '18 at 18:20
  • OTOH, it's not immediately obvious how that would explain `par1` failing and `par2` to `par6` succeeding. A quick test is to declare `line` with the `static` keyword: `static char line[256]`. Give that a try and let me know how it turns out. – user3386109 May 18 '18 at 18:24
  • Thanks [user3386109](https://stackoverflow.com/users/3386109/user3386109) declaring `line` as a `static char` did solve the problem! Could you format your comment as an answer for future references? – Goûtay May 21 '18 at 21:41
  • Cool, glad to hear that worked! You just learned a very important lesson about C programming. Just because a program seems to work (for a simple test) doesn't mean it's actually correct. Beware of undefined behavior! As you gain more experience with C, it will get easier for you to spot (and avoid) undefined behavior. Sure, I'll post an answer. – user3386109 May 21 '18 at 21:59

1 Answers1

0

The problem is that read_name returns the address of a local variable (line). That local variable goes out of scope (and technically ceases to exist) when the function returns. So using the returned pointer results in undefined behavior.

Too see the problem more clearly, here's a simplified version of read_name with only the relevant lines shown:

char *read_name(int par_id, char *filepath){

    char *par_name;

        char line[256];             // line is a local variable
        while (fgets(line, sizeof line, file) != NULL){

                par_name = line;    // par_name now points to the local variable
        }
    }
    return par_name;                // returning the address of the local variable
}

It was noted in the comments under the question that read_name was tested and was found to be functional. So how can it be wrong? That's the worst thing about undefined behavior in C. Sometimes the code appears to work during testing even though it is technically incorrect. And by technically incorrect, I mean that it will break at some point. For example, in the store function, if you add another function call between the call to read_name and the call to fprintf, there's a good chance that name will be corrupted, and won't print properly.

An easy solution in this case is to declare line with the static keyword:

static char line[256];

That way, line has static storage duration, which means it will continue to exist after read_name returns.

user3386109
  • 34,287
  • 7
  • 49
  • 68