Link to home
Start Free TrialLog in
Avatar of mrwad99
mrwad99Flag for United Kingdom of Great Britain and Northern Ireland

asked on

char** as parameter

Ah Good day.

I have a function that reads in lines from an input source and mallocs/reallocs a char** container to be the required size as follows:

int readLines(char** data, FILE* dest, FILE* source)
{
     char oneLine[MAXLENGTH];
     int count;
     if (source == NULL) { printf("the source is damaged"); exit(1); }
     for (count = 0; (fgets(oneLine, sizeof(oneLine), source) != NULL); count++) {
          void* temp = (char**)realloc(data, (20+count)*sizeof(char*));          // Allocate space for 20 MORE lines
          if (temp == NULL) {
               fprintf(stderr, "\nError allocating the required memory for data.\n");
               exit(1);
          } else {
               data = (char**)temp;
          }
          if ( (data[count] = (char*)malloc(strlen(oneLine)+1)) == NULL) {     // Prepare current element to hold one line
               fprintf(stderr, "\nError allocating the space for one line.\n");              
               exit(1);
          } else {
               strcpy(data[count], oneLine);                                             // Copy the line to the current element
          }
     }
     void* temp = realloc(data, count*(sizeof(char*)));                              // Reallocate EXACT amount of space needed
     if (temp != NULL) {
          data = (char**)temp;
     } else {
          fprintf(stderr, "\nError in reallocating exact memory for data.\n");
          exit(0);
     }
     // Works fine here, output as expected...
     int op;
     for (op = 0; op < count; op++) {
          printf("%d: %s", op, data[op]);
     }
     return count;
}

I then attempt to use the following function to output my newly stored data:

void outputLines(char** data, int noLines)
{
     int counter;
     for (counter = 0; counter < noLines; counter++) {
          printf("\n%d: %s\n", counter, data[counter]);          // ** FAILS HERE **
     }  
}    


which is called from main :

int main(void) {
        FILE* ifp;
        FILE* ofp;
        char** lines = NULL;
        ifp = fopen("test.txt", "r");
     ofp = stdout;
        int totalLines = 0;
     if (ifp == NULL) { printf("\nError in opening input file.\n"); exit(1);}
     totalLines = readLines(lines, ofp, ifp);
     outputLines(lines, totalLines);  // Always fails

I found out that after readLines returns, lines is *still* NULL; hence outputLines *will* fail.

But *why* is lines still NULL when I have used pointers ??
     
Avatar of Exceter
Exceter
Flag of United States of America image

>> char** lines = NULL;

You are passing a pointer to a pointer.

Pointers need to point to something. Try declaring lines as a char array.
Avatar of mrwad99

ASKER

Yeah if I say char* lines[] I cannot use realloc in the way I do in readLines.  If you can see how to rewrite readLines to do this, please say, but I was weened off char* [] onto char** for this purpose specifically......
Well, let's start with the crucial question: Why is lines still NULL.

The reason lines is null is because in C everything is passed by value. That is, when you pass an argument to a function, a copy of that variable is placed on the stack. The function is free to use and abuse this variable, and the calling code can never be affected. This avoids the problem of "side effects", where you pass variable to a function and then something "happens to it" that the caller is not aware of, and the programmer has trouble seeing because when he is inspecting the calling code, the source for the called function is typically elsewhere.

Now, it is true of course, that sometimes you *want* the function to affect the argument you passed, and, presumably, you have heard that the path to achieving this behaviour is through the use of double pointers. However, keep in mind that the above description of pass by value remains true, even for double pointers.

The "trick" is as follows. A function *cannot* affect arguments passed in. *BUT* if you give a function a *pointer* to the callers variable, then the function *can* affect a callers variable by following the pointer.

So, specifically, in your case, you should declare:

char **lines=NULL;

Then when you call readLines you should give the function a *pointer* to lines:

    totalLines = readLines(&lines, ofp, ifp);

(note the address of operator). Now the argument to readLines has changed. The first argument should be declared as:

char ***data;

It is still futile for readLines to attempt to change data. But if readLines assignes something to *data, it will actually be assigning it to lines.
Avatar of mrwad99

ASKER

Yeah if I say char* lines[] I cannot use realloc in the way I do in readLines.  If you can see how to rewrite readLines to do this, please say, but I was weened off char* [] onto char** for this purpose specifically......
Avatar of mrwad99

ASKER

OK, I have no rewritten readLines to:

int readLines(char*** data, FILE* dest, FILE* source)
{
     char oneLine[MAXLENGTH];
     int count;
     if (source == NULL) { printf("the source is damaged"); exit(1); }
     for (count = 0; (fgets(oneLine, sizeof(oneLine), source) != NULL); count++) {
          void* temp = (char**)realloc(data, (20+count)*sizeof(char*));          // Allocate space for 20 MORE lines
          if (temp == NULL) {
               fprintf(stderr, "\nError allocating the required memory for data.\n");
               exit(1);
          } else {
               data = (char***)temp;
          }
          if ( (data[count] = (char**)malloc(strlen(oneLine)+1)) == NULL) {     // Prepare current element to hold one line
               fprintf(stderr, "\nError allocating the space for one line.\n");              
               exit(1);
          } else {
               strcpy((char*)data[count], oneLine);                                             // Copy the line to the current element
          }
     }
     void* temp = realloc(data, count*(sizeof(char*)));                              // Reallocate EXACT amount of space needed
     if (temp != NULL) {
          data = (char***)temp;
     } else {
          fprintf(stderr, "\nError in reallocating exact memory for data.\n");
          exit(0);
     }
     // Works fine here, output as expected...
     int op;
     for (op = 0; op < count; op++) {
          printf("%d: %s", op, data[op]);
     }
     return count;
}

which is being called with

totalLines = readLines(&lines, ofp, ifp);

readLines now crashes and burns upon entry.  Try it. My mistake is ??

Thanks greatly.
Avatar of zebada
zebada

Her eis an example of how to reallocate data passed into a function. All it does is append two strings but it reallocates the original string to make room for the appended string. It shows how to pass the string and how to reallocate it and how to access the string data while inside the function.

int append(char** data,char *a)
{
  char *tmp;
  int  len;

  if ( data==NULL ) {
    fprintf(stderr,"Bad argument# 1\n");
    return -1;
  }
  if ( a==NULL ) {
    fprintf(stderr,"Bad argument# 2\n");
    return -1;
  }

  if ( *data==NULL ) {
    if ( (tmp=(char *)malloc(len=strlen(a)+1))==NULL ) {
      fprintf(stderr,"Out of memory\n");
      return -1;
    }
    *tmp = '\0';
  }
  else {
    if ( (tmp=(char *)realloc(*data,len=strlen(*data)+strlen(a)+1))==NULL ) {
      fprintf(stderr,"Out of memory\n");
         return -1;
    }
  }
  *data = tmp;
  strcat(*data,a);
  return len;
}

main(int argc, char *argv[])
{
  char *text=NULL;
  int  len;

  len = append(&text,"Hello");
  printf("Len=%d, [%s]\n",len,text);
  len = append(&text," ");
  printf("Len=%d, [%s]\n",len,text);
  len = append(&text,"World");
  printf("Len=%d, [%s]\n",len,text);

  free(text);
}
Avatar of mrwad99

ASKER

OK, I have no rewritten readLines to:

int readLines(char*** data, FILE* dest, FILE* source)
{
     char oneLine[MAXLENGTH];
     int count;
     if (source == NULL) { printf("the source is damaged"); exit(1); }
     for (count = 0; (fgets(oneLine, sizeof(oneLine), source) != NULL); count++) {
          void* temp = (char**)realloc(data, (20+count)*sizeof(char*));          // Allocate space for 20 MORE lines
          if (temp == NULL) {
               fprintf(stderr, "\nError allocating the required memory for data.\n");
               exit(1);
          } else {
               data = (char***)temp;
          }
          if ( (data[count] = (char**)malloc(strlen(oneLine)+1)) == NULL) {     // Prepare current element to hold one line
               fprintf(stderr, "\nError allocating the space for one line.\n");              
               exit(1);
          } else {
               strcpy((char*)data[count], oneLine);                                             // Copy the line to the current element
          }
     }
     void* temp = realloc(data, count*(sizeof(char*)));                              // Reallocate EXACT amount of space needed
     if (temp != NULL) {
          data = (char***)temp;
     } else {
          fprintf(stderr, "\nError in reallocating exact memory for data.\n");
          exit(0);
     }
     // Works fine here, output as expected...
     int op;
     for (op = 0; op < count; op++) {
          printf("%d: %s", op, data[op]);
     }
     return count;
}

which is being called with

totalLines = readLines(&lines, ofp, ifp);

readLines now crashes and burns upon entry.  Try it. My mistake is ??

Thanks greatly.
Avatar of mrwad99

ASKER

OK, I have no rewritten readLines to:

int readLines(char*** data, FILE* dest, FILE* source)
{
     char oneLine[MAXLENGTH];
     int count;
     if (source == NULL) { printf("the source is damaged"); exit(1); }
     for (count = 0; (fgets(oneLine, sizeof(oneLine), source) != NULL); count++) {
          void* temp = (char**)realloc(data, (20+count)*sizeof(char*));          // Allocate space for 20 MORE lines
          if (temp == NULL) {
               fprintf(stderr, "\nError allocating the required memory for data.\n");
               exit(1);
          } else {
               data = (char***)temp;
          }
          if ( (data[count] = (char**)malloc(strlen(oneLine)+1)) == NULL) {     // Prepare current element to hold one line
               fprintf(stderr, "\nError allocating the space for one line.\n");              
               exit(1);
          } else {
               strcpy((char*)data[count], oneLine);                                             // Copy the line to the current element
          }
     }
     void* temp = realloc(data, count*(sizeof(char*)));                              // Reallocate EXACT amount of space needed
     if (temp != NULL) {
          data = (char***)temp;
     } else {
          fprintf(stderr, "\nError in reallocating exact memory for data.\n");
          exit(0);
     }
     // Works fine here, output as expected...
     int op;
     for (op = 0; op < count; op++) {
          printf("%d: %s", op, data[op]);
     }
     return count;
}

which is being called with

totalLines = readLines(&lines, ofp, ifp);

readLines now crashes and burns upon entry.  Try it. My mistake is ??

Thanks greatly.
You asked *why* lines was still NULL, and I provided an explanation for that. You will now have to spend some time to work out the consequences of your new understanding of the situation. As a first step this line:

    void* temp = (char**)realloc(data, (20+count)*sizeof(char*));          // Allocate space for 20 MORE lines

in readLines will now presumably blow up. The first argument (data) is supposed to be either NULL (in which case realloc will behave like malloc) or point to a malloced block of memory. However with the changes data now contains a pointer to lines, which is a char ** not a malloced block of memory. So, since realloc finds a variable which is neither NULL, nor is it a malloced block of memory, it will fail. This line would have to be adjusted to:


    void* temp = (char**)realloc(*data, (20+count)*sizeof(char*));          // Allocate space for 20 MORE lines

to preserve the original intent.
P.S. sitting on the page and hitting REFRESH will cause you last entry to be resubmitted every time.
Avatar of mrwad99

ASKER

Ok.

Firstly, thanks greatly to Exceter,zebada and imladris for all the advice given.

I will 'process' all of the answers shortly, but before I do:

It is clear that the function readLines accepts a parameter of type char**.  It is also clear that it gets passed a char**.  It is now painfully clear that the char** passed to readLines, ie lines, is *not* being modified by readLines.  Why is it then that I can write:

#include <stdio.h>

void simple (int* local)
{
        *local = 10000000;
}

int main(void)
{
     int var = 10;
     int* ptrVar = &var;
     simple(ptrVar);
     printf("%i", *ptrVar);/* Prints 10000000 not 10 */
}

when 'simple' expects an int*, and gets one, and *does* modify the original variable.  This is why I cannot see why readLines will not modify lines permanently.

Thanks *greatly* once again.
Well, let's try and compare them.

In this main (let's call it simple main), a variable is passed to simple. This variable is a *pointer* to var. Now in the simple function this pointer to var is dereferenced (by writing *local) so that it now has var itself. Then simple assigns 1000000 to that and returns and all is well.

Now, I reread your readLines program a number of times. As far as I can tell you are adding another level of complication there. The declaration:

char **lines;

and the name of the function (readLines) suggests that you are going to want to read multiple lines of text. In order to store these lines of text in memory you will need a 2 dimensional array. This is because a line is an array of characters, and a bunch of lines is thus an array of an array of characters. This is expressed in the declaration of lines.

So, lines is the variable you care about in main. To do the operations that are analogous to your simple main you have to proceed as follows:

When passing lines to readLines, you have to pass it the address of lines (just as simple gets ptrVar, which is the address of var).
The argument to readLines must have one more level of pointer than the variable in main, so data must be "char ***" (just like local is int *, which is one level more than int).
To affect lines from within readLines you must dereference data once (just like in simple, to affect var, you must write "*local").


So, to summarize, the situations are exactly analogous. What I think is confusing you is a couple of things. Firstly, even though lines is a double pointer, it is not analogous to ptrVar; it is in fact analogous to var. The lines variable is the one you want the called function to affect, ptrVar is just a temporary means to an end. You don't care what ptrVar contains at the end of main. But lines is not a temporary means to an end, it is the thing itself, the variable you want readLines to affect, so, again, lines is analogous to var.
Also, passing the function a pointer is only half of the "trick". The other half is that the function must *dereference* the pointer it has been given in order to affect the variable in the calling function.
Avatar of Narendra Kumar S S
mrwad99,
The thing is C functions are called by value.
To change this behavior you will pass address.
Now also, address is a value and changing the address will not reflect back to the called function.
You are doing allocation and reallocation inside readLines(). This will assign new address to data and data is just an address and hence will not change the value of lines passed to it. (data is a copy of lines).
Instead of that, you try to change the value pointed by that address as you have done in the simple() function.
In this function you are not changing the value/address of local. You are chaning the contents of local.
Say, ptrVar has address 0x1234.
You are calling simple with ptrVar as argument. Now 0x1234 will be copied to local.
Now if you change local to 0x1235 inside simple() it will not alter ptrVar.
Instead change the value residing at 0x1234 (that's what you are doing at *local = 10000000;). The same will be visible in the main program also.

Hope you got my point.
Instead of changing the address value, change the contents of that address and the change will be retained.
Hope this will help you to solve your problem.

-Narendra
Avatar of mrwad99

ASKER

ssnkumar and imladris; thank you both for your advice on this.  After studying your comments and my code I found the error of my ways and have corrected this (I believe) in my readLines function:

int readLines(char*** data, FILE* dest, FILE* src)
{
     int count;
     char oneLine[MAXLINELENGTH];
     void* temp = realloc(*data, (20)*sizeof(char*));                        
     if (temp == NULL) {
          fprintf(stderr, "\nError allocating the required memory for data.\n");
          exit(1);
     } else {
          *data = temp;    
     }    
     for (count = 0; fgets(oneLine, sizeof(oneLine), src) != NULL; count++) {
          printf("%i", count);
          if ( (*(data[count]) = malloc(strlen(oneLine)+1)) == NULL) {
               printf("Error in mallocing line");
               exit(1);
          } else {
               strcpy(*(data[count]), oneLine);

          }
     }    
     return count;
}  

However, even though I have read it again and again, it still persists in breaking (VC++ says "data[count] cannot be evaluated) on the 'mallocing' line (*********), if (and only if) count is 3.  All *other* values of count <= 20 (i.e I can replace 'count' in the code with 0, 1, 2 or anything between 4 and 16) and it will work fine.  I have run it through my VC++ debugger several times and come up blank.

I really have followed what everyone has said yet it still denies me !  Please try this for yourself and see that it does crash.  If you could tell me why when I am so close to having this cracked I would infintely appreciate it.

Thanks once again in advance.
Change your references of
*(data[count])
to
(*data)[count]
That should fix your problem :)

Avatar of mrwad99

ASKER

Have tried that and now the code fails to get past count being 0.
I thought it must be *(data[count]) as this is writing directly to the count'th element of data.  Is (data*)[count] not just a cast to type data* ?

Thank you.
I just mocked up this code and it works OK for me.

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

#define MAXLINELENGTH 256

int
readLines(char*** data, FILE* dest, FILE* src)
{
    int count;
    char oneLine[MAXLINELENGTH];
    void* temp = realloc(*data, (20)*sizeof(char*));                        
    if (temp == NULL) {
         fprintf(stderr, "\nError allocating the required memory for data.\n");
         exit(1);
    } else {
         *data = temp;    
    }    
    for (count = 0; fgets(oneLine, sizeof(oneLine), src) != NULL; count++) {
         printf("%i", count);
         if ( ((*data)[count] = malloc(strlen(oneLine)+1)) == NULL) {
              printf("Error in mallocing line");
              exit(1);
         } else {
              strcpy((*data)[count], oneLine);
         }
    }    
    return count;
}  

int
main(int argc, char *argv[])
{
  FILE *src;
  char **data=NULL;
  int  i;

  if ( (src=fopen("data.txt","r"))==NULL )
    return -1;

  readLines(&data,NULL,src);

  for ( i=0 ; i<20 ; i++ )
    printf("%s",data[i]);
  return 0;
}
Avatar of mrwad99

ASKER

OK that actually works now.  But can you *please* tell me why it is (*data)[count] and not *(data[count]) as it is of no long-term benefit if I do not know *how* and *why* it is working.
I figured that *(data[count]) would refer to the actual item that data is pointing to, hence used it.  (*data)[count] looks like a cast of [count] to type (*data), as in (char*)100.

Thank you zebada for that.

-- Confused and awaiting explanation....
ASKER CERTIFIED SOLUTION
Avatar of zebada
zebada

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Ahh crap I did get it wrong.

>Well data is not an array - it's a pointer to the first element of your array, (*data) is the first element of your array. So you need to write to the count'th element of (*data).

Should say:

Well data is not an array - it's a pointer to a pointer to the first element of your array, (*data) is a pointer to the first element of your array. So you need to write to the count'th element of (*data).

Paul

Avatar of mrwad99

ASKER

Fantastic solution zebada, brilliant diagram too.  Thanks also to imladris and all the others who have assisted in providing the answer to this one.

Great thanks to everyone. :-)

Avatar of mrwad99

ASKER

imladris,

I have just been looking back at all my old questions and came across this one; I find it hard to believe that for the advice you gave here I did not award any points.  Mind you, at the time I was not aware that points could be split, or that points could be offered elsewhere for assistance.  I just assumed that there could only be one answer.  I know differently now...

Anyway for the help you gave in this question I would be grateful if you could accept the points I have posted here:

http://oldlook.experts-exchange.com/questions/20792245/Points-for-imladris.html

Thanks a lot :)