Reputation:
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
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
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