What is the point of combining in this code, what is the flaw in the structure?
struct queue_entry_s {
odp_buffer_hdr_t *head;
odp_buffer_hdr_t *tail;
int status;
enq_func_t enqueue ODP_ALIGNED_CACHE;
deq_func_t dequeue;
enq_multi_func_t enqueue_multi;
deq_multi_func_t dequeue_multi;
odp_queue_t handle;
odp_buffer_t sched_buf;
odp_queue_type_t type;
odp_queue_param_t param;
odp_pktio_t pktin;
odp_pktio_t pktout;
char name[ODP_QUEUE_NAME_LEN];
};
typedef union queue_entry_u {
struct queue_entry_s s;
uint8_t pad[ODP_CACHE_LINE_SIZE_ROUNDUP(sizeof(struct queue_entry_s))];
} queue_entry_t;
typedef struct queue_table_t {
queue_entry_t queue[ODP_CONFIG_QUEUES];
} queue_table_t;
static queue_table_t *queue_tbl;
#define ODP_CACHE_LINE_SIZE 64
#define ODP_ALIGN_ROUNDUP(x, align)\
((align) * (((x) + align - 1) / (align)))
#define ODP_CACHE_LINE_SIZE_ROUNDUP(x)\
ODP_ALIGN_ROUNDUP(x, ODP_CACHE_LINE_SIZE)
In the above code typedef union queue_entry_u . What is the meaning of unification. If we take a structure ( typedef struct queue_entry_u ), is there any downside?
source to share
unions
have several use cases:
-
union
saves some memory. It does sos
, andpad
sat in the same place in memory. Helpful if you know you only need one of them you can useunion
. - It's also helpful to be able to iterate over the fields in your structure. By keeping the fields in concatenation, you have both an array and a structure, so if you iterate over
pad
, you are essentially iterating over bytess
. -
unions
also useful in general for casting. The syntax is a little prettier to serialize your entry to a byte array just usingunion
. - In this case, the use
union
is to insert the sizes
according to the cache line. Thus, if the size of aqueue_entry_s
is an exact multiple of the length of the cache lines
, then itpad
will be in exactly the same memory, not in space. Otherwise itpad
will consume more memory thans
, and the sizeunion
will always be a multiple of the cache line length.
That being said, it is generally recommended to use unions
if you are writing inline code for devices with very low memory or very stringent performance requirements. They are very dangerous and very easy to misuse by accidentally writing memory intended to represent a different type in union
.
source to share
Let's start by defining a union from the K&R 2nd edition:
Union is a variable that can contain (at different times) objects of different types [...]. Unions provide a way to manipulate different kinds of data in a single storage area.
The union in the question contains two objects: a type structure struct queue_entry_s
and an array uint8_t
. It is important to note that these two objects overlap in memory. In particular, the address where the structure starts is the same as the address where the array begins. If you write to a structure, the contents of the array will change, and if you write to an array, then the contents of the structure will change.
Then notice that the macro ODP_CACHE_LINE_SIZE_ROUNDUP
takes a size and calculates the smallest multiple of 64 that is greater than or equal to that size.
The size of the union is determined by the size of the largest member. So, for example, if sizeof(struct queue_entry_s)
equal to 80, then the sizeof of the array pad
will be 128 and the size of the union will be 128.
Which brings us finally to the answer. The purpose of the union is to increase the memory used by the structure so that the structure always uses a multiple of 64 bytes of memory.
If you were to change typedef union queue_entry_u
to typedef struct queue_entry_u
, then the memory layout will be changed. Instead of having s
and pad
overlapping in memory, the array pad
will follow the structure s
in memory. Therefore, if it s
occupies 80 bytes and pad
occupies 128 bytes, then it typedef struct queue_entry_u
will define an object that occupies 208 bytes of memory. This will be a waste of memory and will not meet the multi-64 requirements.
source to share