Thread: trouble with arrays of structs and the dynamic allocation thereof

  1. #1
    Registered User
    Join Date
    May 2008
    Posts
    21

    trouble with arrays of structs and the dynamic allocation thereof

    Hi, all--
    I'm working on an airline reservation program, and I think I've gotten myself all turned around with respect to the pointers. Here's the relevant code:

    Code:
    typedef struct
    {
    	int number; //flight number
    	char *origin; //dynamically allocated
    	char *destination; //dynamically allocated
    	int passengerIDs[3][4];
    	short passenger_count;
    } Flight;
    
    void read_flights(const char *filename, Flight **flights, int num_flights, int last_ID)
    {
    	FILE *fp;
    	char fn[80], skip_ch;
    	int i, status;
    	sprintf(fn, "%s.csv", filename);
    	fp = fopen(fn, "r");
    	
    	for(i = 0; status != EOF && i < num_flights; i++)
    	{
    		status = fscanf(fp, "%d%c", flights->number, &skip_ch);
    		while(flights->origin != ',')
    		{
    			fscanf(fp, "%c", flights->origin);
    			flights->origin++;
    		}
    		
    		
    	}//read csv file into flights array
    	
    }//read_flights
    
    int main(int argc, char *argv[])
    {
    	int choice, num_flights, last_ID;
    	get_flights_info(argv[1], &num_flights, &last_ID);
    	Flight *flights[num_flights];
    	read_flights(argv[1], flights, num_flights, last_ID);
    }//main
    My primary problem is this error message: "error: request for member ‘number’ in something not a structure or union"
    It gives the same message for all the lines in red. If I'm understanding this correctly, for some reason, GCC isn't recognizing that flight is a struct. IIRC, C passes arrays as a pointer to the first element, so passing "Flight *flights" would effectively be the same as passing "Flight flights[]", but "Flight **flights" has me completely baffled. As best I can figure, it's... passing the address of the flights array? The instructor assured us that it was necessary, since we would need to dynamically allocate the array.

    Which leads to my second problem: the instructor had no get_flights_info function--I just couldn't figure out how to make GCC stop complaining that *flights[] had no size without first reading the number of flights from the input file. flights[] is also not supposed to be an array of pointers--in short, main() is supposed to read something like this:

    Code:
    int main(int argc, char *argv[])
    {
    	int choice, num_flights, last_ID;
    	Flight flights[num_flights];
    	read_flights(argv[1], &flights, &num_flights, &last_ID);
    }
    I changed it because I kept getting an incompatible pointer type warning for argument 2 of read_flights.

    If anyone could help me out with either of these problems, I'd much appreciate it.

  2. #2
    and the Hat of Guessing tabstop's Avatar
    Join Date
    Nov 2007
    Posts
    14,336
    Quote Originally Posted by quasigreat View Post
    If I'm understanding this correctly, for some reason, GCC isn't recognizing that flight is a struct.
    And the very simple reason for that is that flights is not a struct. For that matter, if flights was a struct, -> wouldn't work either; -> requires a pointer to struct on the left. So more to the point, flights isn't a pointer to struct either. Look at the declaration of flights:

    Code:
    Flight **flights
    Count the number of stars: two. So flights is a pointer to a pointer to a struct. If you had noticed that you had never ever used i anywhere in your for loop, the idea of
    Code:
    flights[i]->number
    might have entered your head. The array notation advances the pointer-to-pointer i spots and returns just a pointer, so that using -> works. (I don't mean advances for real -- just to look it up.)

    EDIT EDIT EDIT: I had originally thought, since you were using ->, that you wanted to have an array of pointers. Upon rereading the whole post again, it looks like you just want an array of Flights to be in *flights. In that case you would want
    Code:
    (*flights)[i].number
    (Since you're passing the "array" by reference with a *, *flights is the "array" itself.)
    Last edited by tabstop; 06-04-2008 at 07:54 PM. Reason: maybe right this time

  3. #3
    and the Hat of Guessing tabstop's Avatar
    Join Date
    Nov 2007
    Posts
    14,336
    Quote Originally Posted by quasigreat View Post
    in short, main() is supposed to read something like this:

    Code:
    int main(int argc, char *argv[])
    {
    	int choice, num_flights, last_ID;
    	Flight flights[num_flights];
    	read_flights(argv[1], &flights, &num_flights, &last_ID);
    }
    You spent pages and pages telling us why the line in red was absolutely wrong (and it is), so your insistence here seems strange. As you said, you should define flights as a Flight *, which you will be interpreting as an array of Flights. (In other words, flights will point to a memory location; adding 1, or 2, or whatever will advance the pointer by that many Flights. Using the [1] notation advances the pointer and dereferences (so that instead of dealing with a Flight *, we're dealing with a Flight itself) so it works out like a "real" array).

    You need to malloc the memory for the flights "array" -- its address will go into the flights variable (this is why the extra level of indirection is necessary), and then with each of those pointers in hand, you need to malloc the memory for each Flight struct that you will need.

  4. #4
    Registered User
    Join Date
    May 2008
    Posts
    21
    OK, I think I understand--like so:
    Code:
    flights = (Flight **)malloc((size_t)*num_flights*sizeof(Flight));
    How would I use this in a for loop? Simply replacing i with flights doesn't seem to work, but using flights[i] defeats me when I reach the next line:
    Code:
    status = fscanf(fp, "&#37;d%c", &(*flights)[i].number, &skip_ch);
    As it appears here, it compiles fine, but there's got to be a more readable way to do that.

    ETA: Just saw your edit of the first post. I assume you're referring to the above line--removing the & results in this warning: "format ‘%d’ expects type ‘int *’, but argument 3 has type ‘int’". Assuming there really isn't a more readable way to do that, it seems to be working fine now, so thank you very much, and I shall embark once more.
    Last edited by quasigreat; 06-04-2008 at 08:45 PM. Reason: Er, *num_flights. *blush* I knew that.

  5. #5
    and the Hat of Guessing tabstop's Avatar
    Join Date
    Nov 2007
    Posts
    14,336
    That assignment should be
    Code:
    *flights = malloc((size_t)*num_flights*sizeof(Flight));
    You shouldn't need to cast malloc, unless you're using C++ all of a sudden. And you don't want to change the value of flights -- that's a local variable, and any changes made to it will vanish when you get back to main. You want to change the value of the variable pointed to.

    It doesn't look marvelous, I admit, but I don't know that you can get rid of much: the & is required when dealing with scanf, the .number tag is necessary so we know what part of the struct we're looking at.

    As to (*flights)[i], if you want to make it look cleaner you can move the ugliness to somewhere else. You might consider borrowing the iterator idea from C++, and have an extra Flight* pointer (call it flight_iterator, or the like) -- your flight number becomes flight_iterator->number (since flight_iterator is a pointer, you need the arrow to get at the member of the struct pointed to) and doing flight_iterator++ will move you to the next Flight in line. All the bits are still there, somewhere, it's just more spread out.

  6. #6
    Registered User
    Join Date
    May 2008
    Posts
    21
    Thanks for pointing that out--you likely saved me a good deal of hair-pulling time. I know I don't need to cast malloc, but the instructor makes us do it anyway. The compiler may be God, but the prof is probably at least the Pope. Or, you know, some madman holding my grade hostage. *g*

    I took another look at the file I'll be reading from, and seeing as each line of flight information may have spaces in it, and each piece of information is delimited by commas, I've changed the code to use fgets to fetch each line, then use strtok to find each one and assign it to the proper component. It makes assignment slightly less ugly--no need for the &. I seem to be having a little trouble figuring out how to reference the pointer components, though:
    Code:
    char *result = NULL;
    result = strtok(info,",");
    while(result != NULL)
    {
    	infoptr[j] = result;
    	result = strtok(NULL, ",");
    	j++;
    }
    
    (*flights)[i].number = (*infoptr)[0];
    (*flights)[i].origin = malloc((strlen(infoptr[1]))*sizeof(char));
    (*flights)[i].*origin = (*infoptr)[1];
    That last line gets me "error: syntax error before ‘*’ token". I'm pretty sure it means the period, but using -> instead doesn't fix it, nor does putting parentheses around *origin.
    Last edited by quasigreat; 06-04-2008 at 09:51 PM. Reason: well, *that* was unexpectedly hideous.

  7. #7
    and the Hat of Guessing tabstop's Avatar
    Join Date
    Nov 2007
    Posts
    14,336
    Quote Originally Posted by quasigreat View Post
    Code:
    (*flights)[i].*origin = (*infoptr)[1];
    If you want to dereference something, you have to dereference the actual thing: origin isn't a thing, until after you use the dot; so the thing you have to dereference is
    Code:
    (*flights)[i].origin
    hence dereferencing it gives
    Code:
    *((*flights)[i].origin)
    The ugliness isn't going away, it seems; and I believe (if I remember my rules of associativity correctly) that all those parentheses are necessary.

    EDIT EDIT EDIT: If origin is supposed to be a character string, then dereferencing won't work -- it will only give you one character. Use strcpy instead.
    Last edited by tabstop; 06-04-2008 at 10:10 PM.

  8. #8
    Registered User
    Join Date
    May 2008
    Posts
    21
    Hmm. This seems straightforward, and yet this gets me "warning: passing argument 1 of ‘strcpy’ makes pointer from integer without a cast"--for both arguments.
    Code:
    strcpy(*((*flights)[i].origin), (*infoptr)[1]);
    I cast them as char pointers, and that gets me "warning: cast to pointer from integer of different size". Er? *backs away slowly*

    ETA: Nevermind, of course it didn't work.
    Code:
    strcpy((*flights)[i].origin, infoptr[1]);
    This, on the other hand, seems to work fine.
    Last edited by quasigreat; 06-04-2008 at 10:52 PM.

Popular pages Recent additions subscribe to a feed