1

I am learning C and apologies if this the question isn't even asked right

But my question is, if I have a typedef struct that contains a union of two other typedef structs, do I need to instantiate all the variables?

For example, if I have the following code:

typedef struct dog {

    char *breed;
    long weight;

} dog;

typedef struct cat {

    bool isBlackCat;
    int numberOfLives;

} cat;



typedef struct animal {

    int id;
    int ownerID;

    char *name;
    bool isDog;

    union {
        struct dog aDog;
        struct cat aCat;
    } animals;

} animal;

If I were to use the above structure in a function like so:

Adoption adoptAnimal(char *nameParam, bool type, int ownerId) {

    Adoption pet = (Adoption) malloc(sizeof(struct animal));

    pet -> name = (char*) malloc((1 + strlen(nameParam)) * sizeof(char));
    strcpy(pet->name, nameParam);


    pet -> id = getId(); //function that generates an id

    pet -> ownerId = ownerId;
    pet -> isDog = type;



    // if type = 1, it's a dog
    if (type) {
        pet -> animals.aDog.breed = some breed; 
        pet -> animals.aDog.weight = some weight; 


    } else {
        pet -> animals.aCat.lives = 9; 
        pet -> animals.aCat.isBlackCat = 1; 

    }

    return pet;
}

Is this proper/legal use of a typedef struct that contains a union? The way the function is set up, the variables from the dog or cat struct will be assigned, but not both, is that allowed? Or do I need to assign all of the variables from both structs

Thank you

ksm
  • 95
  • 7
  • 4
    _the dog or cat struct will be assigned, but not both, is that allowed?_ of course, that's the main purpose of a `union`, share the area in memory but using one at a time. Your code looks fine to me, well, I don't see the `size` member in the `struct aDog` but maybe you are not showing the full code or you mean `weight` instead of `size`. – David Ranieri Apr 17 '20 at 18:58
  • 1
    @DavidRanieri Awesome, thank you! And thanks for the catch, meant to put weight instead of size. Thanks a bunch! – ksm Apr 17 '20 at 19:06
  • 1
    just two other things: first, why do you use `struct dog/cat` inside the animal struct? you used a `typedef` and so could write just `dog/cat`. otherwise you wouldn't need the `typedef`. second, don't cast `void *`: https://stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc – nicksheen Apr 17 '20 at 19:06
  • No need for casts. Suggest `pet->name = malloc(strlen(nameParam) + 1);` – chux - Reinstate Monica Apr 17 '20 at 21:41

1 Answers1

1

This is tricky because of how loosely typed C is. The animals union in the animal struct will be treated as either a dog or a cat, depending on how you are accessing it.

Remember that a union is a single memory location, and it is the size of the largest struct member. This means that calling the struct member animals is misleading, because it is only 1 animal (either a cat or a dog, but never both).

So when you execute pet->animals.aCat.lives = 9, the animals union is treated as a cat struct, and the lives member of that struct is set to 9. However, you could then try and read the animals union as a dog, and C would consider this valid. You would just get data that makes no sense.

I modified your code a little so that it would compile and created a main function to offer some insight about what is going on. The adoptAnimal() function recognizes that the animal being adopted is of type dog, so pet->animals is treated as a dog struct and the members are set accordingly. However, back in the main function, I can go ahead and read the animals union as either a dog or a cat, and C doesn't care.

typedef struct dog {
    char *breed;
    long weight;
} dog;

typedef struct cat {
    bool isBlackCat;
    int numberOfLives;
} cat;

typedef struct animal {
    int id;
    int ownerID;

    char *name;
    bool isDog;

    union {
        struct dog aDog;
        struct cat aCat;
    } animals;
} animal;

animal* adoptAnimal(char *nameParam, bool type, int ownerId) {
    animal *pet = (animal*) malloc(sizeof(struct animal));

    pet->name = (char*) malloc((1 + strlen(nameParam)) * sizeof(char));
    strcpy(pet->name, nameParam);

    pet->id = 1234; //function that generates an id

    pet->ownerID = ownerId;
    pet->isDog = type;

    // if type = 1, it's a dog
    if (type) {
        pet->animals.aDog.breed = malloc(10); 
        strcpy(pet->animals.aDog.breed, "Pit bull");
        pet->animals.aDog.weight = 1000; 

    } else {
        pet->animals.aCat.numberOfLives = 9; 
        pet->animals.aCat.isBlackCat = 1; 

    }

    return pet;
}

int main() {
    animal *pet = adoptAnimal("George", 1, 1234);

    printf("Dog: breed=\"%s\", weight=\"%ld\"\n", pet->animals.aDog.breed, pet->animals.aDog.weight);
    printf("Cat: lives=\"%d\", isBlack=\"%d\"\n", pet->animals.aCat.numberOfLives, pet->animals.aCat.isBlackCat);

    /* Output:
     * Dog: breed="Pit bull", weight="1000"
     * Cat: lives="32735", isBlack="0"
     */
}

So in conclusion, your use of a union with struct members is valid, and you only need to set the values for a dog struct or a cat struct, but not both. Also make sure you use appropriate naming and associated variables to understand what data your union holds.

I like the explanation that tutorialspoint gives on C unions, you might want to check it out if you haven't already.

Hope this clears things up, let me know if there's something I missed.

AJ_
  • 1,455
  • 8
  • 10