Creating structs - memory management

Go To Last Post
7 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi,

 

  I've never really written C for anything other than AVR and so my understanding of how memory is managed is limited.

 

typedef struct Sprite {
    int x;
    int y;
    
    const byte __flash *tile;
    const byte __flash *mask;
} Sprite;

Sprite player = {.x=START_X_POS, .y=START_Y_POS, .tile=&TILES[PLAYER_TILE], .mask=&MASKS[PLAYER_MASK]};

void main(void)
{
    
    for(;;)
    {
        update_player(player);
        
        if (player.y > SCREEH_HEIGHT) // Player dead, respawn
        {
            player = (Sprite){.x=START_X_POS, .y=START_Y_POS, .tile=&TILES[PLAYER_TILE], .mask=&MASKS[PLAYER_MASK]};
        }
    }
}

Given this VERY simple bit of code, if this were to run hundreds of times, would the program run out of memory because it allocates a new chunk of memory each time the player is 'respawned' or does it always reuse the same memory allocated at the beginning when the player variable is first declared and initialised?

 

I'm aware that I *could* 'respawn' like this:

 

player.x=START_X_POS;
player.y=START_Y_POS;

which is fine in something as simple as this example, but sometimes it feels easier to follow if I 'create' a whole new 'object'.

 

In a loosely-related side note - how do I generate readable assembly code from the compiler? I think I end up with a .lss file when I build from AS7 but the code is VERY difficult to follow as it seems to interleave various different bits of functions and code, also I'd like to be able to do the same when I build on my raspberry pi build machine with plain GCC/Makefile.

 

Thank you

-Mike

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Player is only allocated once.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

MalphasWats wrote:
because it allocates a new chunk of memory each time the player is 'respawned'
But I don't see anything doing "dynamic allocation" in that? This line:

Sprite player = {.x=START_X_POS, .y=START_Y_POS, .tile=&TILES[PLAYER_TILE], .mask=&MASKS[PLAYER_MASK]};

creates 8 bytes in memory somewhere and that is the only allocation done. The code then simply rewrites to those same 8 bytes over and over again.

 

In general the only operations that are going to "eat" memory in C/C++ are use of malloc or new and you aren't doing either. If you'd had something like:

Sprite * pPlayers[20];
...
    for(;;)
    {
        update_player(player);
        
        if (player.y > SCREEH_HEIGHT) // Player dead, respawn
        {
            pPlayers[i++] = new Sprite;
        }
    }

or something like that then this would be creating multiple instances.

 

Even here I've imposed a kind of artificial limit on how many there can be as I'm keeping pointers to each newly created Sprite in an array with 20 elements so I have to be sure not to create more than 20! In fact to save the allocation stuff. If I know I will only need 20 (and have the room for 8 * 20 bytes) then it might be "safer" to use:

Sprite Players[20];

and simply create all 20 of them from the start. If I go for the alternatives of:

Sprite * pPlayer = new Sprite;
or
Sprite * pPlayer = malloc(sizeof(Sprite));

then in either case you HAVE to consider the case that pPlayer==NULL after either of those. That basically means "well I asked for another 8 bytes but at this time there were none left". new/malloc return 0 (NULL) when they cannot deliver the requested amount. Too much code uses these kind of allocations and just assume RAM is an infinite resource - that may be true on a PC but it's not on a 1/2/4K RAM micro!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
Too much code uses these kind of allocations and just assume RAM is an infinite resource - that may be true on a PC but it's not on a 1/2/4K RAM micro!

 

Thank you - I'm trying very hard to keep away from malloc where I can. Most of the time I know how much of something I need and try and declare any big structures as globals so they get picked up by avr-size. I was just worried about the 'stack' because it's harder to track and I wasn't sure when it was and wasn't used.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Well you can kind of control the stack usage (unless you do something silly like recursion!). If you have:

void moveplayer(void) {
    int new_x;
    int new_Y;
    uint8_t colorcode;
    
    // stuff
}

then I can tell you that each time this gets called it creates 5 bytes on the stack (together with saved registers and return addresses that you don't get much control over).

 

So as long as you don't do stuff like:

void moveplayer(void) {
    uint8_t spriteBuffer[2048];
    int new_x;
    int new_Y;
    uint8_t colorcode;

or something like that you can be fairly controlled about your dynamic (stack based) allocation.

 

If this code really did call for a 2K buffer then you might have some global 2K that you keep reusing in various places as a "working buffer" rather than making sudden, huge demands on the stack. if it's global then it's fixed at compile time so you know there's always that 2K for use (maybe later you only use the first 800 bytes for something else etc).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If you are using interrupts, you also need to take account of stack usage by ISRs. They need the stack to store the return address, but also must preserve all registers and other CPU state (flags), so there may be a lot of stack usage (push/pop) going on.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

That's probably just 20..30 bytes worse case. If you are operating a system that is within 20/30 bytes of SP hitting .bss you are operating in very dangerous territory !