0

I am new to C language and I am exploring passing by reference, especially char pointer arrays. My first attempt was to split the lines read from a text file, which was stored in list[] in test(). check_length() counts the number of lines in the file.

However, I am still a bit confused with this concept. The second code worked if placed within the main logic but failed after running printf("%s",p); when I tried to split the data via split(). I hope that the pros here can help explain why my code didn't work. Thank you in advance.

Code that worked:

 void split(char **list,int size){
    int count =0;
    char *list2[size][5];
    for(count =1;count<6;count++){
        printf("\n%s",list[count]);
        int count2=0;
        for(char *p = strtok(list[count],"|"); (count2<5)&&p ; p=strtok(NULL,"|"))
        {
            list2[count][count2]=strdup(p);
            printf("\n%d %d %s",count,count2,list2[count][count2]);
            count2++;
        }
    }}

int main(){
int size=0;
size=check_length(size);
printf("%d\n",size);
size = size-4;

char *list[size];
test(list);
split(list,size);
return 0;}

Successful output snippet:

Contactless Thermommeter | CT          | Japan        | 1               | 6
0 0 Contactless Thermommeter
0 1  CT
0 2  Japan
0 3  1
0 4  6

2nd attempt:

void split(char **list, char **list2,char **list3, int size){
   
    int count2=0;
    for(int count = 1;count<size ;count++){
        int count3=0;
        if (count < 6)
        {
            printf("\n%d %s\n",count,list[count]);
            for(char *p = strtok(list[count],"|"); (count3<7)&&p ; p=strtok(NULL,"|"))
            {
                count2 = count-1;
                printf("%s",p);
                list2[count2][count3]=strdup(p);
                printf("\n%d %d %s",count2,count3,list2[count2][count3]);
                count3++;
            }
        }

        else if (count>6){
            printf("\n%d %s\n",count,list[count]);
            for(char *p = strtok(list[count],"|"); (count3<6)&&p ; p=strtok(NULL,"|"))
            {
                count2 = count -7;
                list3[count2][count3]=strdup(p);
                printf("\n%d %d %s",count2,count3,list3[count2][count3]);
                count3++;
            }
            count2++;
        }

    }
}

int main(){
int size=0;
size=check_length(size);
printf("%d\n",size);
size = size-4;

char *list[size];
char *list2[5][7];
char *list3[size-7][6];
test(list);
split(list,list2,list3,size);
return 0;}

Additional question: Is this the correct way to free strdup? I tried free(list2[count2][count3]) and the for loop only ran once, whereas free(p) didn't work.

void split(char **list, char* (*list2)[7], char*(*list3)[6], size_t size){
    int count =0;
    int count2=0;
    for(int count = 1;count<size ;count++){
        int count3=0;
        if (count < 6)
        {
            for(char *p = strtok(list[count],"|"); (count3<7)&&p ; p=strtok(NULL,"|"))
            {
                count2 = count-1;
                list2[count2][count3]=strdup(p);
                printf("\n1 %d %d %s",count2,count3,list2[count2][count3]);
                count3++;
                free(strdup(p));
            }

        }
  • 3
    Side note: I wouldn't use the term *'reference'* not even in C – it collides with other concepts in other languages (C++, Java, C#). In C we have *pointers' available... – Aconcagua Nov 02 '21 at 17:23
  • what does `check_length` do? `test`? Please provide a [mre] – yano Nov 02 '21 at 17:28
  • Appropriate formatting and indentation would make the code more readable (and avoid mixing [indentation styles](https://en.wikipedia.org/wiki/Indentation_style), opt for one and retain that consistently – with my *personal* recommendation being `Allman`). – Aconcagua Nov 02 '21 at 17:28
  • Side note: Correct type for passing array lengths is `size_t`, not `int`. – Aconcagua Nov 02 '21 at 17:31
  • 3
    The first and most important thing to learn about pass by reference in C is that you cannot do it. C has only pass by value. You can achieve behavior similar to what pass by reference would produce by passing pointers (by value). The distinction is sometimes important. – John Bollinger Nov 02 '21 at 17:31
  • 1
    Why are you iterating `count` from 1 to 6? Wouldn't you rather want `0` to size? – Aconcagua Nov 02 '21 at 17:33
  • 1
    In your second attempt, function parameters `list2` and `list3` have types that are inconsistent with the types of the actual arguments passed by `main()`. Your compiler ought to be warning about inconsistent pointer types. If not, then either turn up the warning level until it does, or get a better compiler. – John Bollinger Nov 02 '21 at 17:36
  • Off-topic: You never free the results of `strdup`, thus you get memory leaks! Why do you need those arrays and duplicates at all? You could simply print out the `p` pointer all the time, dropping the `list2` entirely (first version; in second version you could at least store `p` directly without duplicating, your map would contain pointers into the (modified!) original string). – Aconcagua Nov 02 '21 at 17:36
  • About correct pointer types (see @JohnBollinger): These would be `char** list, char* (*list2)[7], char*(*list3)[6]`. I doubt that you want to have these, though. – Aconcagua Nov 02 '21 at 17:45
  • Are you aware that in second version you skip input line at index 6 (you cover <6 and >6, but not ==6). Additionally, as `list2` has length of 5, with `if(count < 6)` you write at `list2[5]`, which is out of bounds, thus undefined behaviour. Are you aware at all that array indices go from 0 to size-1 (as you start counting by 1 as well, in both versions)? – Aconcagua Nov 02 '21 at 17:50
  • Why would one not want to have the correct types, @Aconcagua? – John Bollinger Nov 02 '21 at 17:52
  • @JohnBollinger Hm, bad wording of mine. One would want to have correct types, sure, but I doubt one/QA would want to deal with pointers to arrays, so she/he might rather try a totally different approach. If one really needs pointers to arrays then I'd use variable length ones: `size_t size2, char*(*list2)[size2]`. – Aconcagua Nov 02 '21 at 18:00
  • @Aconcagua Noted about the Allman style and I will try to free the strdup. I wanted to save the data into arrays (same method I used in Python) because my assignment requires me to allow users to modify the data, then write back into the text file. I skipped 1 and 6 because they are the headers for the data, so I used count 2 and 3 for the index. – Louis Chung Nov 03 '21 at 03:29
  • @LouisChung Question is if you want/need to `strdup` at all. It's totally fine to keep pointers into the original string and you can even modify (unless you need to extend in length). If there's a reasonable maximum string length you might even copy the strings to pre-allocated memory, avoiding dynamic memory allocation entirely. – Aconcagua Nov 03 '21 at 09:38
  • About the approach: You seem to be reading in the file line by line in a list of strings – which you are going to split anyway, most likely not further needing the original list entries (which get modified by string tokenising anyway, replacing the separator characters with null characters). So you might rather read in the file line by line and splitt each line *immediately*. As you'd re-use the read buffer then, you then *indeed* need to duplicate the strings, though. – Aconcagua Nov 03 '21 at 09:42
  • As seeing your (now deleted, as actually wasn't one) 'answer': No. `free(strdup(p))` correctly frees, but *yet another* duplicate – `free(p)` would technically be the way to go. However as it was second variant, you wouldn't free within `split` function at all, but *after* having called it (iterating over the arrays), as you need to assume the caller still wants to work with these strings... – Aconcagua Nov 03 '21 at 10:16
  • @Aconcagua noted about your advice. By your last comment, do you mean that I only free in main() after I'm done using the lists? – Louis Chung Nov 03 '21 at 10:41
  • @LouisChung For comparison: `strdup` itself `malloc`s memory which needs to be `free`d to avoid memory leaks. What do you think would happen if `strdup` itself freed the memory already? Guess, the memory wouldn't be available any more to you after the call, so dereferencing the pointer (which you do e.g. on printing) would result in undefined behaviour – possibly, if you're unlucky (or lucky, depending on interpretation...) resulting in your programme crashing – analogously if you free in second variant (not in first as you don't make them available outside of the function). – Aconcagua Nov 03 '21 at 11:03
  • @LouisChung So yes, you'd free within `main` or another function being called from there after you used all these strings (including if you need to replace one). – Aconcagua Nov 03 '21 at 11:05
  • Yet another recommendation: You don't need to deal with all these pointers to array if you give your data more structure. Possibly as follows: `struct Whatever { char* list2[5][7]; char* list3[Maximum][6]; }; ` or maybe with [flexible array member](https://stackoverflow.com/a/14643530/1312382): `char* list3[][6];` (you'll need to store size with) – you can adjust size as needed, but need to `malloc` then – well, or with pointer to array again in the struct (but only there) so that you could `malloc` a sufficiently large array. In any case, your function would just accept `struct Whatever*`... – Aconcagua Nov 03 '21 at 11:19
  • Flexible array member seems most valuable if you know the number of entries in advance, so less suitable if you split right away while iterating (as I recommended in another comment), then you might rather want to go with pointer to array for simpler copying after re-allocation. – Aconcagua Nov 03 '21 at 11:27

0 Answers0