user2856296
user2856296

Reputation:

To pack or not to pack a structure containing just an array

Experiment: Let us declare a container of SHA-512 digest in c/c++ as (using GCC):

#define DIGEST_LENGTH 512
struct Digest {
  uint32_t bits[DIGEST_LENGTH / 8 / sizeof(uint32_t)];
} __attribute__((packed));

Let us not argue over the choice of uint32_t array instead of a char array. Let it be.

And then we can read into the digest from a working buffer in the manner shown below:

Digest digest;
......
memcpy(&digest, buffer, sizeof(Digest));

Similarly we can write the digest to a working buffer:

memcpy(buffer, &digest, sizeof(Digest)); //Assuming sufficient buffer size

My questions:

A. Is the packed attribute necessary and sufficient condition for sizeof(Digest) to always return the correct size (= 512 bits or 64 bytes)?

B. Is digest->bits[i] a safe operation on all architectures while we keep the packed attribute?

C. Can we simplify the representation while keeping the container opaque?

D. Is there a run-time penalty to pay if we keep the representation?

I know that there are other questions regarding the packed attribute, but my question is specifically for a structure contains a single array of basic type.

Upvotes: 2

Views: 467

Answers (2)

Krystian Sakowski
Krystian Sakowski

Reputation: 1653

A. Is the packed attribute necessary and sufficient condition for sizeof(Digest) to always return the correct size (= 512 bits or 64 bytes)?

It is sufficient.

B. Is digest->bits[i] a safe operation on all architectures while we keep the packed attribute?

I think that you do not understand __attribute__((packed)). Below is what is does actually.

When packed is used in a structure declaration, it will compress its fields such, such that, sizeof(structure) == sizeof(first_member) + ... + sizeof(last_member).

Here is the url to the resource of the above statment Effects of __attribute__((packed)) on nested array of structures?

EDIT:

Of course it is safe. Packing defines layout in memory but don't worry because accessing specific data type is handled by the compiler even if data is misaligned.

C. Can we simplify the representation while keeping the container opaque?

Yes, you can just define a simple buffer uint32_t bits[LENGTH]; and it will work in this same manner for you.

D. Is there a run-time penalty to pay if we keep the representation?

Generally speaking yes. Packing enforces compiler to do not perform padding in data structure between members. Padding in data structure makes physical object larger however the access to the singular fields is faster, because it is just read operation do not require read, mask and rotation for instance.

Please check below this very simple program showing the effect of packing on struct size.

#include <stdio.h>
#include <stdint.h>

#pragma pack(push, 1) 
typedef struct _aaa_t {
  uint16_t a;
  uint8_t b;
  uint8_t c;
  uint8_t d;
} aaa_t;
#pragma pack(pop)

typedef struct _bbb_t {
  uint16_t a;
  uint8_t b;
  uint8_t c;
  uint8_t d;
} bbb_t;

int main(void) {
    aaa_t a;
    bbb_t b;
    printf("%d\n", sizeof(a));
    printf("%d\n", sizeof(b));
    printf("%p\n", &(a.a));
    printf("%p\n", &(a.b));
    printf("%p\n", &(a.c));
    printf("%p\n", &(a.d));
    printf("%p\n", &(b.a));
    printf("%p\n", &(b.b));
    printf("%p\n", &(b.c));
    printf("%p\n", &(b.d));
    return 0;
}

Program output:

5
6
0xbf9ea115
0xbf9ea117
0xbf9ea118
0xbf9ea119
0xbf9ea11a
0xbf9ea11c
0xbf9ea11d
0xbf9ea11e

Explanation:

Packed:
     ____________ _______ _______ _______ _______
    |            |       |       |       |       |
    | 0xbf9ea115 | msb_a | lsb_a | lsb_b | lsb_c |
    |____________|_______|_______|_______|_______|
    |            |       |
    | 0xbf9ea119 | lsb_d |
    |____________|_______|

Not Packed:
     ____________ _______ _______ _______ _______
    |            |       |       |       |       |
    | 0xbf9ea11a | msb_a | lsb_a | lsb_b | lsb_c |
    |____________|_______|_______|_______|_______|
    |            |       |       |
    | 0xbf9ea11e | lsb_c |  pad  |
    |____________|_______|_______|

Compiler does that in order to generate code which accesses data types faster than code without padding and memory alignment optimizations.

You can run my code under this link demo program

Upvotes: 1

rici
rici

Reputation: 241931

The struct has only one member, so "packing" it makes no sense. There is no padding between members, because there is no other member.

You might have wanted to pack the array, but that is unnecessary, since uint32_t is an exact-size type. (It is not required to exist, but for architectures which lack uint32_t, the question is irrelevant.)

So if you had some excentric 48-bit architecture in which each "word" is composed of four addressable 12-bit "bytes", you might have a compiler in which an int is three "bytes" long with a four-byte alignment, but you would not have a uint32_t, because the int type is 36 bits, not 32 bits, and (C99 §7.20.1.1, which is included by reference in C++11):

The typedef name intN_t designates a signed integer type with width N, no padding bits, and a two’s complement representation.

Upvotes: 0

Related Questions