Thread: Struct Arrays

  1. #1
    Registered User
    Join Date
    Jun 2008
    Location
    USA
    Posts
    15

    Unhappy Struct Arrays

    Hello All,

    I'm working on a program to parse and interpret command strings for an embedded system, but the issue I'm having is much more basic than string manipulation. I made a function that dynamically allocates memory for a struct array, which I want to be passed back to the calling function. I isolated the code that was giving me the issue and simplified it to show everyone what I'm doing. Here is the code:

    Code:
    /* Parameter units of measure structure */
    typedef enum{
    	UNIT_V,			// Voltage
    	UNIT_A,			// Amperes
    	UNIT_W,			// Watts
    
    	UNIT_rad,		// Radians
    	UNIT_deg,		// Degrees
    	
    	//... (more enums, clipped for simplicity)
    }MeasurementUnits;
    
    typedef struct{
    	ParameterUnit	unit;
    	char*			sValue;
    	float			nValue;
    }CmdParameter;
    
    int main()
    {	
    	CmdParameter* parameters;
    	foo(parameters);
    
    	return 0;
    }
    
    int foo(CmdParameter* P)
    {
    	P = (CmdParameter*)calloc(3, sizeof(CmdParameter*));
    
    	P[0].nValue = 3; // Attempt to assign a value to one of the elements;
    	
    	return 1;
    }
    So in the main function, I create a blank pointer that I want the resulting struct's reference to be assigned to. I then call the foo function, dynamically allocate an array with three elements and then assign that pointer to the variable. I then attempt to assign a value to one of the elements.

    This code compiles with a "'parameters' is used uninitialized in this function" warning but when I execute the code and check the value of element P[0].nValue inside the foo() function, the value never gets assigned to 3.

    I'm assuming that the issue is related to pointer references but the solution is not obvious to me at the moment. If I initialize and dimension the array in the main function, it works fine, but in the real program, I have to be able to do it in the function. I'm sure there's something silly I'm missing here.

    Any help greatly appreciated.

    - Jason O
    Last edited by Jdo300; 03-08-2012 at 11:11 AM.

  2. #2
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by Jdo300
    This code compiles with a "'parameters' is used uninitialized in this function" warning but when I execute the code and check the value of element P[0] inside the foo() function, the value never gets assigned.
    Notice that you are assigning to P in foo. Therefore, P in foo is changed. However, P is a local variable, so you only change a local variable. If you want the change to be reflected in the caller, you must pass a pointer to the object to the function, i.e., you must pass a pointer to a pointer.

    By the way, prefer this:
    Code:
    P = calloc(3, sizeof(*P));
    or if you use a pointer to a pointer:
    Code:
    *P = calloc(3, sizeof(**P));
    The lack of the cast means that you get a warning should you somehow forget to #include <stdlib.h>. Basing sizeof on the result variable means that even if the type of the result variable changes, the code remains correct.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  3. #3
    Registered User
    Join Date
    Nov 2010
    Location
    Long Beach, CA
    Posts
    5,909
    You have two problems I see. First, you're passing in parameters by value (yes, it's a pointer, but you're passing that pointer by value). If you want foo to change what parameters points to in main, you have to pass in &parameters and declare foo to take a double pointer. Second, you only allocate pointers to CmdParameters. You to allocate memory for a whole CmdParameter, not just a pointer to it. Otherwise you don't have valid memory to play with. Oh, I guess there's a third (more minor) problem. Don't cast malloc/calloc/realloc calls. We have a FAQ article that explains why.
    Code:
    int main()
    {
        CmdParameter *parameters;
        foo(&parameters);
    }
    
    int foo(CmdParameter **P)
    {
        *P = calloc(3, sizeof(**P));
        (*P)[0].nValue = 3;
        return 1;
    }
    Notice the sizeof trick I used in the calloc call. If you take whatever you're assigning to (*P in this case), and stick it inside the sizeof call, with one extra * in front of it (**P), you will always get the correct type/size. If you change the type of P, the calloc call still works correctly. And no more allocating space for a pointer instead of the thing.

    EDIT: That C++ witch must have cast a spell to slow down my post so hers got there first!

  4. #4
    Registered User
    Join Date
    Jun 2008
    Location
    USA
    Posts
    15
    Hello,

    Thank you both for the great tips and advice. I was able to fix my little pointer mixup and now that part of the code is functioning correctly.

    I'm still working on debugging my code so I'll definitely ping this thread again if/when I run into any other issues.

    Thanks!
    Jason O

  5. #5
    Registered User
    Join Date
    Jun 2008
    Location
    USA
    Posts
    15
    Quote Originally Posted by anduril462 View Post
    Second, you only allocate pointers to CmdParameters. You to allocate memory for a whole CmdParameter, not just a pointer to it. Otherwise you don't have valid memory to play with.
    OK, I think I just ran into the issue you mentioned here. Using the corrected code that you posted above, I simply changed the index number of P from 0 to 1, and noticed that when I executed the function, it could not assign 3 to the first element. I'm assuming this is occurring because elements 1 and 2 are not being allocated? If this is the case, what is the proper way to allocate memory for all the struct elements and not just the first one?

    Thanks,
    Jason O

  6. #6
    Registered User
    Join Date
    Nov 2010
    Location
    Long Beach, CA
    Posts
    5,909
    Can you post enough of your new code for me to test and replicate the problem?

  7. #7
    Registered User
    Join Date
    Nov 2010
    Location
    Long Beach, CA
    Posts
    5,909
    Can you post enough of your new code for me to test and replicate the problem?

    EDIT: I didn't put it in, but it's good practice to check the return value of malloc/calloc/etc (*P in foo()) to make sure it's not NULL before you use it. I don't think this is the problem though.

  8. #8
    Registered User
    Join Date
    Jun 2008
    Location
    USA
    Posts
    15
    Here's what I did. All I did was add a couple extra lines to the foo function:

    Code:
    int foo(CmdParameter **P)
    {
    	*P = calloc(3, sizeof(**P));
    	
    	(*P)[0].nValue = 3;  // This works fine (value gets assigned)
    	(*P)[1].nValue = 3;  // Value doesn't get assigned
    	(*P)[2].nValue = 3;  // Value doesn't get assigned
    	
        return 1;
    }
    I think part of the problem here is my understanding about how calloc exactly handles the struct array. My understanding was that it allocates enough blank memory for 3 of the array elements, so my assumption was that all three elements would have reserved memory; but the way this is behaving, I'm not so sure.

    - Jason O

  9. #9
    Registered User
    Join Date
    Nov 2010
    Location
    Long Beach, CA
    Posts
    5,909
    Your code works for me. I put it in my own test wrapper, and everything got assigned. How do you know, or how do you determine, that the value doesn't get assigned? I need you to provide the whole test program for me to help you with this, not just the foo function.

  10. #10
    Registered User
    Join Date
    Jan 2009
    Posts
    1,485
    Why do you pass an empty pointer as argument? You could just create it inside foo() and return the pointer with no arguments.

  11. #11
    Registered User
    Join Date
    Jun 2008
    Location
    USA
    Posts
    15
    Quote Originally Posted by anduril462 View Post
    Your code works for me. I put it in my own test wrapper, and everything got assigned. How do you know, or how do you determine, that the value doesn't get assigned? I need you to provide the whole test program for me to help you with this, not just the foo function.
    OK, It looks like the code is actually working fine for me too. The problem was two-fold. 1. I'm using Visual Studio 2010 to step through the code and monitor the variable values in the Watch window. To check the P array (from within the foo() function), I was originally checking each individual value by entering "P[0]->nValue" instead of "(*P)[0].nValue". I *thought* that these two versions of the same expression were supposed to do the same thing, but the first version doesn't show anything in the watch window.

    Then I found out later that the second expression only worked to show the current value of P in the foo function. If I use the same expression to check the value of the parameters pointer like "(*parameters)[0].nValue", from within the main() routine, I would get the error message "can't index into a non-pointer" next to it in the Watch window.

    I then putzed around with it a bit longer and got it to work if I simply entered "parameters[0].sValue" in the watch window. This would show the correct value from within the main() function. BUT, the wacky part was if I used that same expression to look at the value of P from within the foo() function, like "P[0].nValue", it would give me the error: "left of . or -> is not a struct". I'm not completely sure why it is behaving this way but these differences are what made me think there was something wrong with my actual code. Any insights on this?

    - Jason O

  12. #12
    Registered User
    Join Date
    Jun 2008
    Location
    USA
    Posts
    15
    Quote Originally Posted by Subsonics View Post
    Why do you pass an empty pointer as argument? You could just create it inside foo() and return the pointer with no arguments.
    Actually, that would be a good idea, but in the real function that I'm having the problem with, the function returns an int value to indicate rather it failed or not, so I wanted to pass the pointer in as a parameter so that I could still get the int return value.

    - Jason O

  13. #13
    Registered User
    Join Date
    Nov 2010
    Location
    Long Beach, CA
    Posts
    5,909
    Quote Originally Posted by Jdo300 View Post
    OK, It looks like the code is actually working fine for me too. The problem was two-fold. 1. I'm using Visual Studio 2010 to step through the code and monitor the variable values in the Watch window. To check the P array (from within the foo() function), I was originally checking each individual value by entering "P[0]->nValue" instead of "(*P)[0].nValue". I *thought* that these two versions of the same expression were supposed to do the same thing, but the first version doesn't show anything in the watch window.

    Then I found out later that the second expression only worked to show the current value of P in the foo function. If I use the same expression to check the value of the parameters pointer like "(*parameters)[0].nValue", from within the main() routine, I would get the error message "can't index into a non-pointer" next to it in the Watch window.

    I then putzed around with it a bit longer and got it to work if I simply entered "parameters[0].sValue" in the watch window. This would show the correct value from within the main() function. BUT, the wacky part was if I used that same expression to look at the value of P from within the foo() function, like "P[0].nValue", it would give me the error: "left of . or -> is not a struct". I'm not completely sure why it is behaving this way but these differences are what made me think there was something wrong with my actual code. Any insights on this?

    - Jason O
    You have different levels of "pointer-ness" in main() and in foo(). In main, P is a CmdParameter *P. In foo, it's a CmdParameter **P. Note the difference in the number of stars. Thus, to get from the pointer P to the struct, you need a different number of stars (or [ ] brackets, which also dereference). In main, the following are all syntactically valid ways to access a struct member in P (though the last one is most appropriate since P is supposed to be an array).
    Code:
    (*P).nValue  // P points to a struct, *P is the struct it points to, and (*P).nValue is the nValue member of that struct
    P->nValue  // The -> is shorthand for (*P).nValue
    P[0].nValue  // The nValue member of 0th element of the array P
    In foo(), P is a double pointer, there is one more level of indirections. Thus, you can use the same general syntax as I just showed you for main, but you need to add an extra * in front of P to handle that second level of indirection:
    Code:
    (**P).nValue
    (*P)->nValue
    (*P)[0].nValue
    Note the parentheses for grouping. They're necessary because the . -> and [] operators have higher precedence than *.

  14. #14
    Registered User
    Join Date
    Jun 2008
    Location
    USA
    Posts
    15
    Ahhh ok, thank you for clearing that up for me . Now, I have just one more question related to this. I know that since I allocated memory for the struct array with calloc(), is is sufficient to deallocate the entire array simply by calling free(parameters)? I do realize that the string pointer I assign to the struct's sValue variable would need to be separately deallocated.

    - Jason O

  15. #15
    Registered User
    Join Date
    Nov 2010
    Location
    Long Beach, CA
    Posts
    5,909
    Yes. Every call to an allocate function should have a (one) corresponding free. One for each sValue of each parameters element you allocate, and one (just one) for allocating parameters in the first place.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. arrays and struct !!
    By abood1190 in forum C Programming
    Replies: 9
    Last Post: 10-19-2011, 10:26 AM
  2. struct, arrays, srand...
    By jmnp86 in forum C++ Programming
    Replies: 1
    Last Post: 03-15-2011, 02:44 AM
  3. struct arrays
    By silentkarma in forum C++ Programming
    Replies: 9
    Last Post: 07-23-2008, 03:24 AM
  4. Problem with struct arrays...
    By Bizmark in forum C Programming
    Replies: 5
    Last Post: 03-27-2008, 07:46 PM
  5. about arrays create within a struct~
    By Unregistered in forum C Programming
    Replies: 2
    Last Post: 04-26-2002, 04:21 AM