0

I am trying to solve a rather challenging programming task. We are given the following 64-bit structure, which allows us to store a point in time accurate to the minute:

typedef struct
{
  unsigned char day;  //1 Byte
  unsigned char month; //1 Byte
  unsigned int year;  //4 Bytes
  unsigned char hours; //1 Byte
  unsigned char minutes; //1 Byte
} Time; //4 Bytes are probably used for padding

This structure has the size of 12 Bytes (I checked this and the struct really does use so much space). The task is to reduce the size to 8 Bytes and we are not allowed to use unions. We are supposed to use a lot of these structures, hence why we want to reduce the memory size.

The only thing I can think of is to change the unsigned int to unsigned short, but how can we get rid of the other two Bytes?

Kind regards

klutt
  • 30,332
  • 17
  • 55
  • 95
  • 3
    Move `year` to be first in the struct to avoid padding. Why do you need so much space for `year` btw? – Ted Lyngmo Sep 01 '22 at 10:26
  • Are you allowed to change the order of elements? – the busybee Sep 01 '22 at 10:27
  • Yes we are allowed to change the order –  Sep 01 '22 at 10:27
  • 2
    1) start with moving year to the top 2) play with [pragma pack](https://stackoverflow.com/questions/3318410/pragma-pack-effect) if memory efficiency is essential – Alexander Dmitriev Sep 01 '22 at 10:28
  • @Luxdragon Are you allowed to change the types too? If you change `year` to an `unsigned char`, you'll get the size down to 5. – Ted Lyngmo Sep 01 '22 at 10:28
  • Assuming a 16-bit `unsigned short` for the year, then 1 + 1 + 2 + 1 + 1 is 6 and, even with two padding bytes, it will be only 8 bytes in total. (But, with that change, I get a size of 6 bytes on my 64-bit Windows/MSVC system: padding is not required, because the `year` field is already offset by a multiple of 2 bytes.) – Adrian Mole Sep 01 '22 at 10:29
  • @Ted Storing a year of (say) 1966 would be tricky in an `unsigned char`. – Adrian Mole Sep 01 '22 at 10:33
  • @AdrianMole Not if the epoch for this particular time system is 1966-01-01 00:00:00 :-) – Ted Lyngmo Sep 01 '22 at 10:35
  • Thank a lot for your help! The solution was to just move the unsigned int to the top and yes we could replace the unsigned int with unsigned short to obtain a 6 Byte structure or even a 5 Byte structure, if we set year to unsigned char. Why does just moving the order save so much space? –  Sep 01 '22 at 10:35
  • 1
    @user3121023 That's a very bad idea in general... I'd only consider packing the data nibble-wise if RAM is extremely sparse, such as when using a low end 8 -it microcontroller. And even then it's a last resort. – Lundin Sep 01 '22 at 10:42
  • Don't post answers in the question – klutt Sep 01 '22 at 11:15
  • 1
    You can use a single `unsigned int` because 8000*12*31*24*60 = 4285440000 which is less than 2^32 (and 8000 years should be enough for everyone). – n. m. could be an AI Sep 01 '22 at 11:25
  • If you put the year at the top of the structure, then maybe you should reorder the month and day fields so that the time components are in decreasing order. It's more for tidiness than out of necessity. – Jonathan Leffler Sep 01 '22 at 12:25

3 Answers3

3

The main problem here is that char have no alignment requirement, but int has a requirement of 4 byte alignment (on a 32 bit system). Meaning it must start at an address divisible by 4. Structs are guaranteed to start at an aligned address, so what you get is likely this:

unsigned char day;  //1 Byte
unsigned char month; //1 Byte
// 2 byte padding!
unsigned int year;  //4 Bytes
unsigned char hours; //1 Byte
unsigned char minutes; //1 Byte
// 2 byte padding!

The first two padding bytes are there to ensure that the int is aligned, the last two are there to ensure that the next struct in an array of structs start at an aligned address.

The fix is simple, just move year to the top of the struct:

unsigned int year;  //4 Bytes
unsigned char day;  //1 Byte
unsigned char month; //1 Byte
unsigned char hours; //1 Byte
unsigned char minutes; //1 Byte

And now the struct should be 8 bytes large with zero padding.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • Your answer is also correct :D –  Sep 01 '22 at 10:40
  • @Luxdragon Hey at least I won #FGITW by beating Ted by 30 seconds :) – Lundin Sep 01 '22 at 10:43
  • 1
    @Luxdragon please *vote* if you think an answer is correct. When I ask a question, I alway upvote every answer just as thanks for their trouble to reply (unless the answer is rubbish). – Weather Vane Sep 01 '22 at 10:44
  • I didn't know that :D so the green tick goes to you. I wish I could upvote, but I don't have sufficient reputation points –  Sep 01 '22 at 10:50
3

Your current struct, since sizeof(Time) == 12 and sizeof(unsigned int) == 4 is layed out like this:

typedef struct
{
  unsigned char day;  //1 Byte
  unsigned char month; //1 Byte
// 2 bytes padding to align the `unsigned int`
  unsigned int year;  //4 Bytes
  unsigned char hours; //1 Byte
  unsigned char minutes; //1 Byte
// 2 bytes padding
} Time; 

You can reduce the size to 8 by moving year first. No padding needed here:

typedef struct
{
  unsigned int year;  //4 Bytes
  unsigned char day;  //1 Byte
  unsigned char month; //1 Byte
  unsigned char hours; //1 Byte
  unsigned char minutes; //1 Byte
} Time; 
Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
  • Hey your answer is also correct :D –  Sep 01 '22 at 10:50
  • @Luxdragon Glad to hear it! At least I was first to comment on the issue, but Lundin beat me to it to post an actual answer by 30 seconds :-) – Ted Lyngmo Sep 01 '22 at 11:11
0

The compiler aligns the data based on the alignment of the next field of a structure, in your case you have two char fields that have alignment of 1 (any address is valid to hold a char) but the int type (32bit) has an alignment requirement of 4bytes, so, as the offset when the second char element is 2, it needs to add two alignment spaces to make it properly aligned (to an address multiple of 4) and it also makes the whole struct to be 4 aligned, so when the struct end arrives, there's an alignment of 4 for the next structure of this type (to properly conserve the alignment of the full structure in arrays) and so, it needs to add two more bytes to the end of the structure. This makes the four bytes you observe in your code.

To optimize, just put the large fields first on the structure, and fill it later at the end with the smaller data (that has less alignment requirements) This way, the structure aligns better with the bigger fields and the fields tend to fill the holes made by alignment. if you had used, instead:

struct my_struct {
    double        time_as_double;  // normally 8 byte alignment
    char         *timezone;        // 8 byte alignment in 64bit architectures.
    int           year;            // 4 byte alignment
    unsigned char month,           // one byte alignment
                  mday,            // one byte alignment
                  hour,            // one byte alignment
                  min,             // one byte alignment
                  sec;             // one byte alignment
    // seven more bytes of alignment to comply with 8 byte alignment for the
    // full structure (to allow it to form arrays)
};

you will get a 32 byte structure (a worst case, as it resulted in 25 packed bytes, so the next multiple of 8 is 32)

#include <stdio.h>

struct my_struct {
    double        time_as_double;  // normally 8 byte alignment
    char         *timezone;        // 8 byte alignment in 64bit architectures.
    int           year;            // 4 byte alignment
    unsigned char month,           // one byte alignment
                  mday,            // one byte alignment
                  hour,            // one byte alignment
                  min,             // one byte alignment
                  sec;             // one byte alignment
    // seven more bytes of alignment to comply with 8 byte alignment for the
    // full structure (to allow it to form arrays)
};

int main()
{
    printf("sizeof (struct my_struct) == %zu\n", sizeof (struct my_struct));
}

which produces:

$ a.out
sizeof (struct my_struct) == 32
$ _
Luis Colorado
  • 10,974
  • 1
  • 16
  • 31