Thread: Structs, returning pointers, passing by reference, the deference operator and arrays

  1. #1
    Registered User
    Join Date
    Sep 2016
    Posts
    41

    Structs, returning pointers, passing by reference, the deference operator and arrays

    Hey guys,

    I have been doing some serious practice with structs and I have set myself a little project. The idea is a basic user registration process. I have been working on this for about two weeks but I've become really stuck the last couple of days.

    The flow of the code is this:

    I've created a set of data structs for various different types of info. Then a user template struct which is a struct of pointers to all the other structs. I want a way to dynamically create and allocate this information as new users are created, on demand. So I've created a function that returns a pointer to a mallocated space. After asking for a last name (as a small test exercise), it inputs the name to a pseudo-random number generator and generates a system and user id. This all works just fine. The problem I have is passing the pointers by reference to the function that inputs the appropriate data to the new struct.

    Here is the code:

    Code:
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    
    /*
     * 
     */
    
    
    struct systemid_struct {
        
        uint systemID;
        uint userID;
        
    };
    
    
    struct personalname_struct {
        
        char firstName[24];
        char lastName[24];
        
        
    };
    
    
    struct username_struct {
        
        char userName[24]; //enter a comparison function so that usernames are not the same as first or last name
      
    };
    
    
    struct email_struct {
        
        char email[32];
        
    };
    
    
    struct password_struct {
        
        char password[25]; //enter the same comparison function for passwords, look up how to securely store passwords in C
        
        
    };
    
    
    struct dob_struct {
        
        short age;
        short day;
        short month;
        uint year;
            
    };
    
    
    struct physical_struct {
        
        float weight;
        float height;
    
    
    };
    
    
    
    
    struct user_template_struct {
    
    
       struct systemid_struct *sytemtemp;
       struct personalname_struct *personaltemp;
       struct username_struct *usernametemp;
       struct email_struct *emailtemp;
       struct password_struct *passwdtemp;
       struct dob_struct *detailstemp;
       struct physical_struct *physicaltemp;
    
    
    };
    
    
    // write a fn that creates a user template struct of pointers for each user
    
    
    struct user_template_struct *createuserfn (){
        
        struct user_template_struct *userptr = malloc(sizeof(*userptr));
        
        return userptr; 
    }
    
    
    int fillbasicinfo(struct user_template_struct *userptr, uint* systemID, uint* userID, char* lastName){
    
    
    for(uint i=0; i<1; i++){
            userptr->sytemtemp->systemID = *systemID;
            userptr->sytemtemp->userID = *userID;
            printf("fillbasicinfo USERID: %u\n fillbasicinfo SYSTEMID: %u\n", userptr->sytemtemp->userID, userptr->sytemtemp->systemID);
        }
        
        for(uint i=0; i<24; i++){
       
            userptr->personaltemp->lastName[i] = lastName[i];
            printf("fillbasicinfo lastNAME: %c\n", lastName[i]);
        }
    
    
    
    
        
    return 0;
    }
    
    
    // then write a fn that stores all the users within another struct of pointers
    
    
    
    
    int main(int argc, char** argv) {
        
        uint systemID;
        uint userID; 
        char lastName[24];
        
        uint* systemIDptr;
        uint* userIDptr;
        char* lastNameptr;
        
        struct user_template_struct *userlistptr;
        
        systemIDptr = &systemID;
        userIDptr = &userID;
        lastNameptr = &lastName[0];
        
        
        printf("%s", "Welcome, please Enter your last name \n");
        scanf("%s", lastName);
        printf("Your last name is: %s \n", lastName);
        printf("%s", "Thank you, please wait a moment \n");
            
        srand(strlen(lastName));
        uint randomid = rand();
        userID = randomid;
        printf("Your user ID is: %u \n", userID);
        srand(userID);
        systemID = rand();
        printf("Your system ID is: %u \n", systemID);
        
        userlistptr = createuserfn();
        printf("\n The struct for the new user is at address: %p\n", userlistptr);
        
        
        int userfillerror = fillbasicinfo(&userlistptr, &systemIDptr, &userIDptr, &lastNameptr);
        
        
        
        printf("%d", userfillerror);
        
        
        return (EXIT_SUCCESS);
    }
    and here is the initial ouput:

    Code:
    cd '/home/access/NetBeansProjects/username_practice'
    /usr/bin/make -f Makefile CONF=Debug
    "/usr/bin/make" -f nbproject/Makefile-Debug.mk QMAKE= SUBPROJECTS= .build-conf
    make[1]: Entering directory '/home/access/NetBeansProjects/username_practice'
    "/usr/bin/make"  -f nbproject/Makefile-Debug.mk dist/Debug/GNU-Linux/username_practice
    make[2]: Entering directory '/home/access/NetBeansProjects/username_practice'
    mkdir -p build/Debug/GNU-Linux
    rm -f "build/Debug/GNU-Linux/main.o.d"
    gcc    -c -g -MMD -MP -MF "build/Debug/GNU-Linux/main.o.d" -o build/Debug/GNU-Linux/main.o main.c
    main.c: In function ‘main’:
    main.c:161:39: warning: passing argument 1 of ‘fillbasicinfo’ from incompatible pointer type [-Wincompatible-pointer-types]
         int userfillerror = fillbasicinfo(&userlistptr, &systemIDptr, &userIDptr, &lastNameptr);
                                           ^
    main.c:105:5: note: expected ‘struct user_template_struct *’ but argument is of type ‘struct user_template_struct **’
     int fillbasicinfo(struct user_template_struct *userptr, uint* systemID, uint* userID, char* lastName){
         ^~~~~~~~~~~~~
    main.c:161:53: warning: passing argument 2 of ‘fillbasicinfo’ from incompatible pointer type [-Wincompatible-pointer-types]
         int userfillerror = fillbasicinfo(&userlistptr, &systemIDptr, &userIDptr, &lastNameptr);
                                                         ^
    main.c:105:5: note: expected ‘uint * {aka unsigned int *}’ but argument is of type ‘uint ** {aka unsigned int **}’
     int fillbasicinfo(struct user_template_struct *userptr, uint* systemID, uint* userID, char* lastName){
         ^~~~~~~~~~~~~
    main.c:161:67: warning: passing argument 3 of ‘fillbasicinfo’ from incompatible pointer type [-Wincompatible-pointer-types] 
         int userfillerror = fillbasicinfo(&userlistptr, &systemIDptr, &userIDptr, &lastNameptr);
                                                                       ^
    main.c:105:5: note: expected ‘uint * {aka unsigned int *}’ but argument is of type ‘uint ** {aka unsigned int **}’
     int fillbasicinfo(struct user_template_struct *userptr, uint* systemID, uint* userID, char* lastName){
         ^~~~~~~~~~~~~
    main.c:161:79: warning: passing argument 4 of ‘fillbasicinfo’ from incompatible pointer type [-Wincompatible-pointer-types]
         int userfillerror = fillbasicinfo(&userlistptr, &systemIDptr, &userIDptr, &lastNameptr);
                                                                                   ^
    main.c:105:5: note: expected ‘char *’ but argument is of type ‘char **’
     int fillbasicinfo(struct user_template_struct *userptr, uint* systemID, uint* userID, char* lastName){
         ^~~~~~~~~~~~~
    mkdir -p dist/Debug/GNU-Linux
    gcc     -o dist/Debug/GNU-Linux/username_practice build/Debug/GNU-Linux/main.o 
    make[2]: Leaving directory '/home/access/NetBeansProjects/username_practice'
    make[1]: Leaving directory '/home/access/NetBeansProjects/username_practice'
    
    
    BUILD SUCCESSFUL (total time: 234ms)

    As you can see it compiles fine, eventually with no errors. But of course, the code doesn't execute properly. I have tried to declare the parameters as double pointers but then my build completely fails.

    Here is the result from running the code:

    Code:
    Welcome, please Enter your last name 
    asdfasdfasdf
    Your last name is: asdfasdfasdf 
    Thank you, please wait a moment 
    Your user ID is: 1687063760 
    Your system ID is: 374848126 
    
    
     The struct for the new user is at address: 0xd88830
    fillbasicinfo USERID: 2024348112
     fillbasicinfo SYSTEMID: 2024348116
    fillbasicinfo lastNAME: �
    fillbasicinfo lastNAME: 
    fillbasicinfo lastNAME: �
    fillbasicinfo lastNAME: x
    fillbasicinfo lastNAME: �
    fillbasicinfo lastNAME: 
    fillbasicinfo lastNAME: 
    fillbasicinfo lastNAME: 
    fillbasicinfo lastNAME: �
    fillbasicinfo lastNAME: 
    fillbasicinfo lastNAME: �
    fillbasicinfo lastNAME: x
    fillbasicinfo lastNAME: �
    fillbasicinfo lastNAME: 
    fillbasicinfo lastNAME: 
    fillbasicinfo lastNAME: 
    fillbasicinfo lastNAME: �
    fillbasicinfo lastNAME: 
    fillbasicinfo lastNAME: �
    fillbasicinfo lastNAME: x
    fillbasicinfo lastNAME: �
    fillbasicinfo lastNAME: 
    fillbasicinfo lastNAME: 
    fillbasicinfo lastNAME: 
    0
    RUN FINISHED; exit value 0; real time: 4s; user: 0ms; system: 0ms
    I understand how to write to an array and read from it via a pointer, it's not rocket science. All the operators seem fine and autofill gives me the correct array when I use the deference operator. I have practiced reading to and writing from arrays via structs and pointers using the deference operator, I can't see what would be different here.

    Why am I getting these garbage values? And why is the System/UserID's different when output from inside the function than from the initial generation?

    I have tried declaring the parameters as double pointers and also tried to pass them directly i.e. without the ampersand. Both of these ideas resulted in build fail.

    It should be noted that this all worked just fine when fillbasicinfo and createuserfn were one function. But I felt that to be rather un-kosher for C. Hence this approach.

    Thanks in advance
    MedicineMan25

  2. #2
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    37,537
    > struct user_template_struct *userptr = malloc(sizeof(*userptr));
    After doing this, you need to do all of these as well.

    userptr->sytemtemp = malloc ( sizeof *userptr->sytemtemp );
    // ditto for all other pointer members


    To be honest, I'd simplify your approach to remove all the pointers from user_template_struct so that everything is allocated properly in the first malloc call.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  3. #3
    Registered User
    Join Date
    Sep 2016
    Posts
    41
    oh... wait, wut? That was sort of the point, I thought that by putting those pointers into the user_template_struct it would then declare structs of their respective types and allocate the space based on their sizes... If I remove them then how will all of those instances get created?

    Perhaps there is a neater and more C kosher way to put this together... I will have a long think and see what I come up with.

    Thanks Salem.

  4. #4
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    37,537
    I'm suggesting that you don't try and eat an elephant all in one go!

    Get something basic working without all the malloc complication.

    When version 1 works, you can save it in your favourite Version control system, then enhance it on a per member basis, starting with changing
    Code:
    struct user_template_struct {
       struct systemid_struct sytemtemp;
       struct personalname_struct personaltemp;
       struct username_struct usernametemp;
       struct email_struct emailtemp;
       struct password_struct passwdtemp;
       struct dob_struct detailstemp;
       struct physical_struct physicaltemp;
    };
    into
    Code:
    struct user_template_struct {
       struct systemid_struct *sytemtemp; //!! this is now a pointer
       struct personalname_struct personaltemp;
       struct username_struct usernametemp;
       struct email_struct emailtemp;
       struct password_struct passwdtemp;
       struct dob_struct detailstemp;
       struct physical_struct physicaltemp;
    };
    The things you need to do are
    - allocate and free space at appropriate places in the code.
    - use -> in place of . to access the now pointed to data.

    Repeat as necessary.

    Where you decide certain things are optional (say email details), then you need to specifically make the pointer NULL and test for this whenever you try to deal with email.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  5. #5
    Registered User
    Join Date
    Sep 2016
    Posts
    41
    Ok, cool. Sounds like I need to build this a little slower. It just sort of evolved, as things tend to do in code. I used malloc because I thought I needed to in order to preserve the local memory from inside the function, now I realise that I have confused with local variables, which are not the same thing in this instance. So I don't need to malloc this at all?

    Also, I haven't used the dot operator at all, that isn't how you access members via a pointer, I know this...


    Thanks for taking the time Salem, I really value your input.

  6. #6
    Registered User
    Join Date
    Sep 2016
    Posts
    41
    It seems the solution is to use strcpy. like this:

    Code:
    void fillinfofn(struct user_template_struct *userptr, char* lastName){
    
    
        for(uint i=0; i<strlen(lastName); i++){
       
            strcpy(userptr->personaltemp->lastName, lastName);
            printf("fillbasicinfo lastNAME: %c\n", lastName[i]);
        }
        
    }
    I have used this with arrays before so it's no real surprise, what I AM confused about is why I can't transfer character by character inside a for loop. like this:

    Code:
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    
    /*
     * 
     */
    
    
    struct user_template_struct {
    
    
        int age;
        int weight;
        char array[];
    
    
    };
    
    
    struct user_template_struct *createuserfn (){
        
        struct user_template_struct *userptr = malloc(sizeof(*userptr));
            
        return userptr; //figure out a way to access this information through a database.
    }
    
    
    int main(int argc, char** argv) {
    
    
    char lastName[24] = "Millar-Powell";
    
    
        struct user_template_struct *userlistptr;
    
    
        userlistptr = createuserfn();
        printf("%p\n", userlistptr);
       
        for(uint i=0; i<strlen(lastName); i++){
        userlistptr->array[i] = lastName[i];
        printf("the lastName array contains: %c\n", lastName[i]);    
        printf("the struct array contains: %c\n", userlistptr->array[i]);
        }
        
        printf("your last name is: %s\n", userlistptr->array);    
        
        return (EXIT_SUCCESS);
    }
    It seems the only differentiating factors between these two pieces of code (the block just above and the original post block) are accessing members inside a nested struct and possibly that I am passing the pointer to the struct, by reference into a function. If these are the reasons then so be it, yay though I walk through the valley of data structures I shall fear no instance.

    However, It would be really helpful if someone has an explanation for this seemingly strange behaviour, so that I and all may learn. If it is in fact just one of those weird behaviours that we all sit back and say "hey yeh that's really weird, how interesting you can't do such and such with array members inside nested structs when you pass a pointer by reference", which we then take note of on this day and learn for future, then that is cool too. In that light speculations are welcome.

    Cheeyaz betchehz

  7. #7
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    37,537
    It won't work because you don't have a size in your array.
    > char array[];

    To fix this, you need to measure the length of the string you want to add, then do something like
    struct user_template_struct *userptr = malloc(sizeof(*userptr) + lengthOfStringToCopyIntoArray );


    Nor will it work because your per-character copy loop does NOT copy the \0 character.

    Also, calling strlen() in the control part of a for loop is exceedingly wasteful.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  8. #8
    Registered User
    Join Date
    Sep 2016
    Posts
    41
    Well no, that version with the non-sized array works. However, yes I know I should have declared a sized array as not doing so CAN lead to undefined behaviour. Anyway, It seems using a for loop only works when its accessing a single level struct. That's the question: why cant I use a for loop for accessing array members of a NESTED struct.

    The version that DOESN'T work has a declared-size array, but is accessing array members of a nested struct. In that scenario, the solution was to use strcpy. A for loop will not work, oddly enough.

    >Also, calling strlen() in the control part of a for loop is exceedingly wasteful.

    Oh ok, I didn't know that. Is there a more efficient way of measuring a string inside an array of this nature? i.e. not everyone's last name will be 25 characters.

    But, I'm guessing that whatever I save by not iterating all the way through an array, is negligible compared to calling strlen inside the for loop, true?

    Thanks again Salem!

  9. #9
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    37,537
    > However, yes I know I should have declared a sized array as not doing so CAN lead to undefined behaviour.
    There is no CAN, it is ALWAYS undefined behaviour - even if your program is apparently working correctly.

    > That's the question: why cant I use a for loop for accessing array members of a NESTED struct.
    Probably because you're doing it wrong.

    Study example
    Code:
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    struct username_struct {
        char userName[24];
    };
    
    struct user_pointer_struct {
       struct username_struct *usernametemp;
    };
    
    struct user_member_struct {
       struct username_struct usernametemp;
    };
    
    struct user_pointer_struct *new_1 ( ) {
      struct user_pointer_struct *new = malloc( sizeof(*new) );
      new->usernametemp = malloc( sizeof(*new->usernametemp) );
      return new;
    }
    void free_1 ( struct user_pointer_struct *p ) {
      free( p->usernametemp );
      free( p );
    }
    
    struct user_member_struct *new_2 ( ) {
      struct user_member_struct *new = malloc( sizeof(*new) );
      return new;
    }
    void free_2( struct user_member_struct *p ) {
      free( p );
    }
    
    
    void fill1 ( struct user_pointer_struct *p, struct user_member_struct *m ) {
      strcpy( p->usernametemp->userName, "Salem" );
      strcpy( m->usernametemp.userName, "MedicineMan25" );
    }
    
    void fill2 ( struct user_pointer_struct *p, struct user_member_struct *m ) {
      int i;
      char *u1 = "Salem";
      char *u2 = "MedicineMan25";
      for ( i = 0 ; u1[i] != '\0' ; i++ ) {
        p->usernametemp->userName[i] = u1[i];
      }
      p->usernametemp->userName[i] = '\0';
    
      for ( i = 0 ; u2[i] != '\0' ; i++ ) {
        m->usernametemp.userName[i] = u2[i];
      }
      m->usernametemp.userName[i] = '\0';
    }
    
    void print1 ( struct user_pointer_struct *p, struct user_member_struct *m ) {
      printf("User1 = %s\n", p->usernametemp->userName );
      printf("User2 = %s\n", m->usernametemp.userName );
    }
    
    void print2 ( struct user_pointer_struct *p, struct user_member_struct *m ) {
      int i;
      printf("User1 = " );
      for ( i = 0 ; p->usernametemp->userName[i] != '\0' ; i++ ) {
        printf("%c", p->usernametemp->userName[i] );
      }
      printf("\n");
    
      printf("User2 = " );
      for ( i = 0 ; m->usernametemp.userName[i] != '\0' ; i++ ) {
        printf("%c", m->usernametemp.userName[i] );
      }
      printf("\n");
    }
    
    int main ( ) {
      struct user_pointer_struct *up = new_1();
      struct user_member_struct *um = new_2();
    
      fill1( up, um );
      print1( up, um );
    
      fill2( up, um );
      print2( up, um );
    
      free_1( up );
      free_2( um );
      return 0;
    }
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. passing array of structs to function by reference
    By greadey in forum C Programming
    Replies: 5
    Last Post: 08-15-2015, 06:52 AM
  2. importance of returning reference in operator overloading.
    By vaibhavs17 in forum C++ Programming
    Replies: 20
    Last Post: 05-13-2009, 12:28 PM
  3. Pointers to objects -- passing and returning pointers
    By 1veedo in forum C++ Programming
    Replies: 4
    Last Post: 04-04-2008, 11:42 AM
  4. Passing pointers to two-dimensional arrays of structs
    By dr.neil.stewart in forum C Programming
    Replies: 2
    Last Post: 09-07-2007, 10:25 AM
  5. passing structs & pointers to structs as arguments
    By Markallen85 in forum C Programming
    Replies: 6
    Last Post: 03-16-2004, 07:14 PM

Tags for this Thread