Home > Back-end >  How can I modify a C struct by an array index?
How can I modify a C struct by an array index?

Time:06-20

I am working with structs in a game I am making, and the struct holds information about the humanoid (position, speed, etc). I have a struct for the player and one for a zombie. I'm adding the zombie struct onto an array (and plan on doing the same for other enemies) in order to iterate over them every frame loop and advance them one step forward or work game logic out. (For the record, the game runs on the Nintendo DS). Unfortunately, I can't seem to modify values of the struct via an array index, as the values don't change. Modifying the struct directly works, but it would be a huge pain to update every struct independently (reason for looping through an array of the structs). Here is the code that goes in more depth:

typedef struct{
    int x;
    float y;
    float speed;
    bool isSwordActive;
    bool facingRight;
} humanoid;

void game(){
    humanoid player = {12, 12, 0, false, false};
    humanoid zombie = {226, 12, 0, false, false};
    humanoid objects[1] = {zombie};

    while(1){
        // This works
        zombie.y  = zombie.speed;
        zombie.speed  = 0.125;

        // This does not work
        for(int i = 0; i < sizeof(objects)/sizeof(objects[0]); i  ){
            objects[i].y  = objects[i].speed;
            objects[i].speed  = 0.125;
        }
    }
}

Could someone please explain why this doesn't work, and a possible solution? Thank you!

CodePudding user response:

objects should be a pointer array. You were copying the data into the array, so the original objects were untouched.

Here is the refactored code. It is annotated:

#define bool int
#define false 0
#define true 1

typedef struct {
    int x;
    float y;
    float speed;
    bool isSwordActive;
    bool facingRight;
} humanoid;

void
game()
{
    humanoid player = { 12, 12, 0, false, false };
    humanoid zombie = { 226, 12, 0, false, false };

// ORIGINAL
#if 0
    humanoid objects[1] = { zombie };
// FIXED
#else
    humanoid *objects[2] = { &zombie, &player };
#endif

// ORIGINAL
#if 0
    while (1) {
        // This works
        zombie.y  = zombie.speed;
        zombie.speed  = 0.125;

        // This does not work
        for (int i = 0; i < sizeof(objects) / sizeof(objects[0]); i  ) {
            objects[i].y  = objects[i].speed;
            objects[i].speed  = 0.125;
        }
    }
#endif

// BETTER
#if 0
    while (1) {
        // This works
        zombie.y  = zombie.speed;
        zombie.speed  = 0.125;

        // This does not work
        for (int i = 0; i < sizeof(objects) / sizeof(objects[0]); i  ) {
            objects[i]->y  = objects[i]->speed;
            objects[i]->speed  = 0.125;
        }
    }
#endif

// BEST
#if 1
    while (1) {
        // This works
        zombie.y  = zombie.speed;
        zombie.speed  = 0.125;

        // This does not work
        for (int i = 0; i < sizeof(objects) / sizeof(objects[0]); i  ) {
            humanoid *obj = objects[i];
            obj->y  = obj->speed;
            obj->speed  = 0.125;
        }
    }
#endif
}

In the above code, I've used cpp conditionals to denote old vs. new code:

#if 0
// old code
#else
// new code
#endif

#if 1
// new code
#endif

This works, thank you! Pointers are always screwing me over. If you don't mind, could you explain why the best solution is better than the better solution? Is it performance-wise? Wouldn't performance slow down a bit if there are too many objects and making new variables for each object? Thank you! – JuanR4140

We're not creating/copying the contents of the array element (e.g. 20 bytes). We're just calculating the address of the array element. This must be done anyway, so no extra code.

This is a fundamental difference and I think this is still a point of confusion for you.

If we compile without optimization, the "BETTER" solution would reevaluate objects[i] three times. Before it can "use" object[i], the value there must be fetched and placed into a CPU register. That is, it would do:

regA = &objects[0];
regA  = i * sizeof(humanoid);
regA = *regA;
regA->something

Again, this is repeated 3 times:

regA = &objects[0];
regA  = i * sizeof(humanoid);
regA = *regA;
regA->something
regA = &objects[0];
regA  = i * sizeof(humanoid);
regA = *regA;
regA->something
regA = &objects[0];
regA  = i * sizeof(humanoid);
regA = *regA;
regA->something

If we compile with optimization, the generated code would be similar to the "BEST" solution. That is, the compiler realizes that objects[i] is invariant within the given loop iteration, so it only needs to calculate it once:

regA = &objects[0];
regA  = i * sizeof(humanoid);
regA = *regA;
regA->something
regA->something
regA->something

Note that the compiler must always put the calculated address value into a register before it can use it. The compiler will assume that obj should be in a register. So, no extra code generated.

In the "BEST" version, I "told" the compiler how to generate the efficient code. And, it would generate the efficient code without optimization.

And, doing obj->something in several different places is simpler to read [by "humanoid" programmers ;-)] than objects[i]->something in multiple places.

This would show its value even more with a 2D array.

  • Related