I was looking to make a struct which was an arbitrary size, known at compile time. (for use in a macro).
eg:
/* assume sizeof(SomeStruct) could be an odd number,
* if it is using GCC's 'packed' attribute for eg */
struct {
unsigned char data[sizeof(SomeStruct)];
} a, *tmp;
tmp = (void *)some_data
a = *tmp;
However I was concerned that struct padding may increase the size of the struct so it is larger than the member, I was assured that the size of a single member struct will always be the size of its member.
So my question is:
Can I rely on single member structs always being the same size as their member? Is this apart of the C spec? or is it just how most compilers behave?
C11 6.7.2.1 paragraph 17:
There may be unnamed padding at the end of a structure or union.
No special case is listed for structs with only one member. This is implementation-defined.
It's not obvious why a compiler would put padding there though, since the size of the member should already be as padded as is necessary for all purposes, as it has a complete type of its own.
For your usage though, you don't need to rely on the compiler's behaviour - you can enforce it yourself by adding a static assertion to your macro that sizeof *tmp == sizeof (SomeStruct). If you have an up-to-date compiler and your macro allows for declaration statements, just use _Static_assert; there are also several C99-compatible hacks you can use, such as:
#define STATIC_EXPR_ASSERT(COND) (sizeof (char[(COND) ? 1 : -1]))
... which you can use as the lhs of a comma-expression.
You should also note that if the body of the temporary struct is made up of a char array, it may not have sufficient alignment to represent the incoming data correctly. From C11 onwards you should specify the alignment of the array with _Alignas:
struct {
_Alignas (max_align_t) unsigned char data[sizeof(SomeStruct)];
} a, *tmp;
...which will ensure it can safely store the data for any type regardless of alignment. In C99 you don't have any way to explicitly request max alignment, but you can force the alignment to match a named type by making the temporary struct a member of a union alongside it (you might try long double and hope that's the most-aligned type).
Related
This question already has answers here:
Are C-structs with the same members types guaranteed to have the same layout in memory?
(4 answers)
Closed 1 year ago.
I'm trying to understand the so-called "common initial sequence" rule for C aliasing analysis. This question does not concern C++.
Specifically, according to resources (for example the CPython PEP 3123),
[A] value of a struct type may also be accessed through a pointer to the first field. E.g. if a struct starts with an int, the struct * may also be cast to an int *, allowing to write int values into the first field.
(emphasis mine).
My question can be roughly phrased as "does the ability to access a struct by pointer to first-member-type pierce nested structs?" That is, what happens if access is via a pointer whose pointed-to type (let's say type struct A) isn't exactly the same type as that of the first member (let's say type struct B), but that pointed-to type (struct A) has common first initial sequence with struct B, and the "underlying" access is only done to that common initial sequence?
(I'm chiefly interested in structs, but I can imagine this question may also pertain to unions, although I imagine unions come with their own tricky bits w.r.t. aliasing.)
This phrasing may not clear, so I tried to illustrate my intention with the code as follows (also available at godbolt.org, and the code seem to compile just fine with the intended effect):
/* Base object as first member of extension types. */
struct base {
unsigned int flags;
};
/* Types extending the "base" by including it as first member */
struct file_object {
struct base attr;
int index;
unsigned int size;
};
struct socket_object {
struct base attr;
int id;
int type;
int status;
};
/* Another base-type with an additional member, but the first member is
* compatible with that of "struct base" */
struct extended_base {
unsigned int flags;
unsigned int mode;
};
/* A type that derives from extended_base */
struct extended_socket_object {
struct extended_base e_attr; /* Using "extended" base here */
int e_id;
int e_type;
int e_status;
int some_other_field;
};
/* Function intended for structs "deriving from struct base" */
unsigned int set_flag(struct base *objattr, unsigned int flag)
{
objattr->flags |= flag;
return objattr->flags;
}
extern struct file_object *file;
extern struct socket_object *sock;
extern struct extended_socket_object *esock;
void access_files(void)
{
/* Cast to pointer-to-first-member-type and use it */
set_flag((struct base *)file, 1);
set_flag((struct base *)sock, 1);
/* Question: is the following access defined?
* Notice that it's cast to (struct base *), rather than
* (struct extended_base *), although the two structs share the same common
* initial member and it is this member that's actually accessed. */
set_flag((struct base *)esock, 1);
return;
}
This is not safe as you're attempting to access an object of type struct extended_base as though it were an object of type struct base.
However, there are rules that allow access to two structures initial common sequence via a union. From section 6.5.2.3p6 of the C standard:
One special guarantee is made in order to simplify the use of unions: if a union contains several structures that share a common initial sequence (see below), and if the union object currently contains one of these structures, it is permitted to inspect the common initial part of any of them anywhere that a declaration of the completed type of the union is visible. Two structures share a common initial sequence if corresponding members have compatible types (and, for bit-fields, the same widths) for a sequence of one or more initial members
So if you change the definition of struct extended_socket_object to this:
struct extended_socket_object {
union u_base {
struct base b_attr;
struct extended_base e_attr;
};
int e_id;
int e_type;
int e_status;
int some_other_field;
};
Then a struct extended_socket_object * may be converted to union u_base * which may in turn be converted to a struct base *. This is allowed as per section 6.7.2.1 p15 and p16:
15 Within a structure object, the non-bit-field members and the units in which bit-fields reside have addresses that increase
in the order in which they are declared. A pointer to a structure
object, suitably converted, points to its initial member (or
if that member is a bit-field, then to the unit in which it
resides), and vice versa. There may be unnamed padding within
a structure object, but not at its beginning.
16 The size of a union is sufficient to contain the largest of its members. The value of at most one of the
members can be stored in a union object at any time. A
pointer to a union object, suitably converted, points to each
of its members (or if a member is a bit-field, then to the
unit in which it resides), and vice versa.
It is then allowed to access b_attr->flags because of the union it resides in via 6.5.2.3p6.
According to the C Standard (6.7.2.1 Structure and union specifiers, paragraph 13):
A pointer to a structure object, suitably converted, points to its
initial member (or if that member is a bit-field, then to the unit in
which it resides), and vice versa.
So, converting esock to struct extended_base * and then converting it to unsigned int * must give us a pointer to the flags field, according to the Standard.
I'm not sure if converting to to struct base * counts as "suitably converted" or not. My guess is that it would work at any machine you will try it on, but I wouldn't recommend it.
I think it would be safest (and also make the code more clear) if you simply keep a member of type struct base inside struct extended_base (instead of the member of type unsigned int). After doing that, you have two options:
When you want to send it to a function, write explicitly: esock->e_attr.base (instead of (struct base *)esock). This is what I would recommend.
You can also write: (struct base *) (struct extended_base *) esock which is guaranteed to work, but I think it is less clear, and also more dangerous (if in the future you will want to add or accidentaly add another member in the beginning of the struct).
After reading up into the standard's text following the other answers (thanks!!) I think I may try to answer my own question (which was a bit misleading to begin with, see below)
As the other answers pointed out, there appear to be two somewhat overlapping concerns in this question -
"common initial sequence" -- in the standard documents this specifically refers to the context of a union having several structs as member and when these member structs share some compatible members beginning from the first. (§6.5.2.3 " Structure and union members", p6 -- Thanks, #dbush!).
My reading: the language spec suggests that, if at the site of access to these "apparently" different structs it is made clear that they actually belong to the same union, and that the access is done through the union, it is permitted; otherwise, it is not.
I think the requirement is meant to work with type-based aliasing rules: if these structs do indeed alias each other, this fact must be made clear at compile time (by involving the union). When the compiler sees pointers to different types of structs, it can't, in the most general case, deduce whether they may have belonged to some union somewhere. In that case, if it invokes type-based alias analysis, the code will be miscompiled. So the standard requires that the union is made visible.
"a pointer (to struct), when suitably converted, points to its initial member" (§6.7.2.1 "Structure and union specifiers", p15) -- this sounds tantalizingly close to 1., but it's less about aliasing than about a) the implementation requirements for struct and b) "suitable conversion" of pointers. (Thanks, #Orielno!)
My reading: the "suitable conversion" appears to mean "see everything else in the standard", that is, no matter if the "conversion" is performed by type cast or assignment (or a series of them), being "suitable" suggests "all constraints must be satisfied at all steps". The "initial-member" rule, I think, simply says that the actual location of the struct is exactly the same as the initial member: there cannot be padding in front of the first member (this is explicitly stated in the same paragraph).
But no matter how we make use of this fact to convert pointers, the code must still be subject to constraints governing conversion, because a pointer is not just a machine representation of some location -- its value still has to be correctly interpreted in the context of types. A counterexample would be a conversion involving an assignment that discards const from the pointed-to type: this violates a constraint and cannot be suitable.
The somewhat misleading thing in my original post was to suggest that rule 2 had something to do with "common initial sequence", where it is not directly related to that concept.
So for my own question, I tend to answer, to my own surprise, "yes, it is valid". The reason is that the pointer conversion by cast in expression (struct base *)esock is "legal in the letter of the law" -- the standard simply says that (§6.5.4 "Cast operators", p3)
Conversions that involve pointers, other than where permitted by the constraints of 6.5.16.1 (note: constraints governing simple assignment), shall be specified by means of an explicit cast.
Since the expression is indeed an explicit cast, in and by itself it doesn't contradict the standard. The "conversion" is "suitable". Further function call to set_flag() correctly dereferences the pointer by virtue of the suitable conversion.
But! Indeed the "common initial sequence" becomes important when we want to improve the code. For example, in #dbush's answer, if we want to "inherit from multiple bases" via union, we must make sure that access to base is done where it's apparent that the struct is a member of the union. Also, as #Orielno pointed out, when the code makes us worry about its validity, perhaps switching to an explicitly safe alternative is better even if the code is valid in the first place.
In the language the C Standard was written to describe, an lvalue of the form ptr->memberName would use ptr's type to select a namespace in which to look up memberName, add the offset of that member to the address in ptr, and then access an object of that member type at that address. Once the address and type of the member were determined, the original structure object would play no further rule in the processing of the expression.
When C99 was being written, there was a desire to avoid requiring that a compiler given something like:
struct position {double x,y,z; };
struct velocity {double dx,dy,dz; };
void update_positions(struct positions *pp, struct velocity *vv, int count)
{
for (int i=0; i<count; i++)
{
positions[i].x += vv->dx;
positions[i].y += vv->dy;
positions[i].z += vv->dz;
}
}
must allow for the possibility that a write to e.g. positions[i].y might affect the object of vv->dy even when there is no evidence of any relationship between any object of type struct position and any object of type struct velocity. The Committee agreed that compilers shouldn't be required to accommodate interactions between different structure types in such cases.
I don't think anyone would have seriously disputed the notion that in situations where storage is accessed using a pointer which is freshly and visibly converted from one structure type to another, a quality compiler should accommodate the possibility that the operation might access a structure of the original type. The question of exactly when an implementation would accommodate such possibilities should depend upon what its customers were expecting to do, and was thus left as a quality-of-implementation issue outside the Standard's jurisdiction. The Standard wouldn't forbid implementations from being willfully blind to even the most obvious cases, but that's because the dumber something would be, the less need there should be to prohibit it.
Unfortunately, the authors of clang and gcc have misinterpreted the Standard's failure to forbid them from being obtusely blind to the possibility that a freshly-type-converted pointer might be used to access the same object as a pointer of the original type, as an invitation to behave in such fashion. When using clang or gcc to process any code which would need to make use of the Common Initial Sequence guarantees, one must use -fno-strict-aliasing. When using optimization without that flag, both clang nor gcc are prone to behave in ways inconsistent with any plausible interpretation of the Standard's intent. Whether one views such behaviors as being a result of a really weird interpretation of the Standard, or simply as bugs, I see no reason to expect that gcc or clang will ever behave meaningfully in such cases.
I came across definition of a struct that looked something like this in the linux kernel sources (net/ipv4/fib_trie.c)-
struct key_vector {
t_key key;
unsigned char pos; /* 2log(KEYLENGTH) bits needed */
unsigned char bits; /* 2log(KEYLENGTH) bits needed */
unsigned char slen;
union {
/* This list pointer if valid if (pos | bits) == 0 (LEAF) */
struct hlist_head leaf;
/* This array is valid if (pos | bits) > 0 (TNODE) */
struct key_vector __rcu *tnode[0];
};
};
What does above definition of tnode mean? I wrote a sample code to understand and print that looks like -
struct s {
union {
int i;
int *pi[0];
};
};
int main()
{
struct s s1;
s1.i = 0x12345678;
printf("sizeof(s1): %lu, s1.i: %x, s1.pi: %p, s1.*pi: %p\n", sizeof(s1), s1.i, s1.pi[0], s1.pi);
}
The output of which is -
sizeof(s1): 8, s1.i: 12345678, s1.pi: 0x7ffc12345678, s1.*pi: 0x7ffc2824add0
I am not entirely sure I understand this.
This is a common practice known as "the struct hack" or flexible array member. When allocating memory for the struct, you add additional space. Then that extra space can be accessed using the array member at the end of the struct. Since C99, you can omit the size entirely.
Prior to the ratification of C89, many compilers would interpret a declaration like someType member[someConst]; within a structure as advancing the "next item offset" to a multiple of someType's alignment requirement, associating symbol member with that offset, interpreting someConst as an unsigned number, and then advancing the "next member offset" by someConst * sizeof someType. They would behave in this fashion without regard for whether someConst was zero. Because some programs relied upon compilers processing zero-length array declarations this way, but some members of the Committee didn't want to require that all compilers support them, the authors of the Standard opted to say that implementations may support zero-sized arrays or not, at their leisure, provided that when fed any program that exploits zero-sized arrays they issue at least one diagnostic (which programmers could then ignore at their leisure when using an implementation that usefully supported the construct).
What most people are concerned about is what happens if they receive a byte array with data and they want to cast the array to a struct pointer - this can violate strict aliasing rules. I'm not sure whether initializing an empty byte array of sufficient size, casting it to a struct pointer, and then populate the struct members would violate the strict aliasing rules.
The details:
Say I have 2 packed structs:
#pragma pack(1)
typedef struct
{
int a;
char b[2];
uint16_t c : 8;
uint16_t d : 7;
uint16_t e : 1;
} in_t;
typedef struct
{
int x;
char y;
} out_t;
#pragma pack()
I have many types of in/out packed structs for different messages so please ignore the specific members I put for the example. The structs can contain bitfields, other structs, and unions. Also, endianess is taken care of. Also, I can't use new c standards (>= c99) features.
I'm receiving a buffer containing in_t (the buffer is large enough to contain out_t, however big it'll be) as void *
void recv_msg(void *data)
{
in_t *in_data = (in_t*)data;
out_t *out_data = (out_t*)data;
// ... do something with in_data then set values in out_t.
// make sure values aren't overwritten.
}
Now I have a new type of in struct
#pragma pack(1)
typedef struct
{
int a;
char b[3];
uint32_t c;
} in_new_api_t;
typedef struct
{
int x;
char y[2];
} out_new_api_t;
#pragma pack()
Now, when moving to the new api but keeping the old api for backward compatibility, I want to copy values from the old in_t to in_new_api_t, use in_new_api_t, set values in out_new_api_t, and then copy the values to out_t.
The way I thought of doing it is to allocate an empty byte array the size of max(sizeof(in_new_api_t), sizeof(out_new_api_t));, cast it to in_new_api_t *, translate values from in_t to in_new_api_t, send the new api struct to the new api function, then translate values from out_new_api_t to out_t.
void recv_msg(void *data)
{
uint8_t new_api_buf[max(sizeof(in_new_api_t), sizeof(out_new_api_t))] = {0};
in_new_api_t *new_in_data = (in_new_api_t*)new_api_buf;
in_t *in_data = (in_t*)data;
// ... copy values from in_data to new_in_data
// I'M NOT SURE I CAN ACCESS MEMBERS OF new_in_data WITHOUT VIOLATING STRICT ALIASING RULES.
new_in_data->a = in_data->a;
memcpy(new_in_data->b, in_data->b, 2);
// ...
new_recv_msg((void*)new_in_data);
out_new_api_t *new_out_data = (out_new_api_t*)new_api_buf;
out_t *out_data = (out_t*)data;
// ... copy values from new_out_data to out_data
}
The point I'm just not sure about is whether casting from 'uint8_t []' to 'in_new_api_t *' would violate the strict aliasing rules or cause any other issues. Also Access performance issues are a concern.
And if so, what is the best solution?
I can make copies of in_t and out_t and make in_new_api_t point to data but then I need to copy the data 4 times to make sure I'm not overwriting values: from data to in_t tmp_in, from tmp_in to in_new_api, then from out_new_api to out_t tmp_out and from tmp_out to out_t out.
It sounds like what you want is a couple of union types. The common initial sequences of the struct members of a union are layout-compatible, per the standard, and can be mapped onto each other, in exactly the same way as the family field of every sockaddr_* type. Type-punning on a union is legal in C, but not in C++ (although it works with POD on every compiler, no compiler that tries to be compatible with existing code will ever break it, and any possible alternative is undefined behavior too). This might possibly obviate the need for a copy.
A union is guaranteed to be properly-aligned for both. If you do use pointers, it is probably a good idea to Alignas the object to both types, just in case.
A memcpy() to and from arrays of unsigned char is also legal; the language standards call the contents of the array after the copy the object representation.
It is fairly straight-forward:
Casting to a pointer-to-struct type, when the pointed-at data by the void* is of any different type, is a strict aliasing violation.
Casting to a pointer-to-struct from a pointer to raw character buffer is a strict aliasing violation. (You may however go the other way around: from pointer-to-struct into pointer-to-char.)
So your code looks wildly unsafe and is also a bit confusing because of the void pointer. So number one is to get rid of that icky, dangerous void pointer! You can create a type such as:
typedef union
{
in_t old;
in_new_api_t new;
uint8_t bytes [sizeof(in_new_api_t)];
} in_api_t;
Then use this as parameter to your function.
This will first of all allow you to access the initial parts of each struct in a safe manner that doesn't violate aliasing (6.5.2.3, the rule about common initial sequence). That is, the members a and b will correspond to each other in both structs. The only thing you can't rely on is the members that aren't the same - those will have to be copied explicitly with memcpy.
Second, you can now use the bytes member when you need to serialize the data. If you write the "out" structures as unions in a similar manner, and they too contain a bytes member of exactly the same size, you can safely cast from one type to the other, without strict aliasing violations. This is allowed by C11 6.5:
An object shall have its stored value accessed only by an lvalue expression that has one of the following types:
- a type compatible with the effective type of the object
/--/
- an aggregate or union type that includes one of the aforementioned types among its members
If your union is accessed by a pointer to union type, that includes a byte array of exactly the same size (a compatible type), then that's allowed.
What you are doing in recv_msg() clearly is undefined behaviour and will likely break your code some day, as the compiler is entitled to do whatever it wants when moving from *in_data to *out_data. Also, if the void* data argument doesn't come from either a malloc() (and cousins) or from an object that originally was an in_t then you have UB and alignment problems even there.
Your method to save RAM is extremely risky. Even if you are bold enough to ignore the more theoretical UB case of accessing memory with an illegal but correctly aligned type, you still will get problems as there simply is no guarantee that the order of operations of copying in-place from one struct to the other won't trash your data.
I am unsure of whether or not the code has pointer aliasing (or other standard conformance issues) in the asserts cast. It seems that a pointer to the union type should be able to be cast to a pointer of the first member and since the union is only composed of these two structs, I think a cast to the first member should work, but I'm not sure if this is correct or if I'm glossing over padding details in the process. Are unions required to pad the upper bits?
It seems as this is unspecified behavior? Does anyone have any insight as to whether this is suported. I know that there is an alternative standard way of doing this by using a struct with a enum type field and struct container_storage member, but it seems like a waste of space considering that this information is already in struct contained
compilation command in linux: gcc -std=c99 -Wextra -pedantic -fstrict-aliasing test.c && ./a.out && echo $? returns 0
#include <stdlib.h>
#include <assert.h>
enum type {type_a = 1, type_b = 2};
struct contained {
int some_other_field;
enum type type;
};
struct container_a {
struct contained contained;
int test;
};
struct container_b {
struct contained contained;
char test;
};
union container_storage {
struct container_a container_a;
struct container_b container_b;
};
int
main(int argc, char **argv)
{
union container_storage a =
{.container_a = {.contained = {.type = type_a}, .test = 42}};
union container_storage b =
{.container_b = {.contained = {.type = type_b}, .test = 'b'}};
assert(((struct contained *)&a)->type == type_a);
assert(((struct contained *)&b)->type == type_b);
return EXIT_SUCCESS;
}
References:
[1] gcc, strict-aliasing, and casting through a union
[2] What is the strict aliasing rule?
That should be fine. C11, 6.5.2.3/6 ("Structure and union members") says:
One special guarantee is made in order to simplify the use of unions: if a union contains
several structures that share a common initial sequence (see below), and if the union
object currently contains one of these structures, it is permitted to inspect the common
initial part of any of them anywhere that a declaration of the completed type of the union
is visible. Two structures share a common initial sequence if corresponding members
have compatible types (and, for bit-fields, the same widths) for a sequence of one or more
initial members.
(C++ makes the same guarantee (C++11, 9.2/18) for standard-layout unions.)
union don't pad, they just overlay their members. The first member of any struct is guaranteed to start right off, without padding. In general struct that start with the same members of same type are guaranteed to have the same layout for that initial part.
Under C89, a pointer of structure type which identifies a member of a union may be used to inspect any member which is part of a Common Initial Sequence shared with the type of data stored therein. This in turn generally implies that a pointer to any structure type could be used to inspect any member of the Common Initial Sequence shared with any other type (such behavior would have been unambiguously defined if the object happened to be a member of a declared union object, and the only practical way for a compiler to yield the required behavior in those cases would be to uphold it for all).
C99 added an additional requirement that the CIS guarantees only apply when a complete union type containing both structures is visible, which some compiler writers seem to think means it only applies to accesses performed directly through union types. The authors of such compilers seem to think a function that would need to handle functions with a common header like:
struct smallThing { void *next; uint16_t length; uint8_t dat[2]; };
struct bigThing { void *next; uint16_t length; uint8_t dat[65528]; };
should be to extract out the header like:
struct uHeader { void *next; uint16_t length; };
struct smallThing { uHeader head; uint8_t dat[2]; };
struct bigThing { uHeader head; uint8_t dat[15994]; };
or use union-type objects for everything, even though using uHeader would
increase the size of struct smallThing by 50% (and totally break any code that
had been reliant upon its layout), and using unions for everything when most objects only need to be small would increase memory usage a thousandfold.
If one needs code to be compatible with compilers that essentially ignore the Common Initial Sequence rule, one should regard the Common Initial Sequence rule as essentially useless. Personally, I think it would be better to document that only compilers that honor the CIS should be considered suitable for use with one's code, rather than bending over backward to accommodate unsuitable compilers, but I think it's important to be aware that compilers like the latter ones exist.
So far as I can tell, clang and gcc do not honor the CIS rule in any useful way except when the -fno-strict-aliasing flag is set. I don't know about other compilers.
struct test{
unsigned long int asd[][3][6];
};
sizeof(struct test) returns 0. So, if that is an exact alias of
struct test{
unsigned long int asd[0][3][6];
};
is there any practical use for such a field declaration? You may consider also the template metaprogramming stuff, which is always surprising.
The first example demonstrates the use of a flexible array member, a feature of C99. However in order to get that snippet to compile, you need to have another member in your struct, ie:
struct test{
int a;
unsigned long int asd[][3][6];
};
This documentation on gcc tells you why sizeof evaulates to zero, and the syntactical difference of a normal array:
In ISO C90, you would have to give contents a length of 1, which means
either you waste space or complicate the argument to malloc.
In ISO C99, you would use a flexible array member, which is slightly
different in syntax and semantics:
Flexible array members are written as contents[] without the 0.
Flexible array members have incomplete type, and so the sizeof operator may not be applied. As a quirk of the original implementation
of zero-length arrays, sizeof evaluates to zero.
Flexible array members may only appear as the last member of a struct that is otherwise non-empty.
A structure containing a flexible array member, or a union containing such a structure (possibly recursively), may not be a
member of a structure or an element of an array. (However, these uses
are permitted by GCC as extensions.)
It's a "flexible array member", described in section 6.7.2.1 paragraph 16 of the C99 standard (3.7MB PDF).
This is a new feature in C99; some compilers (particularly Microsoft's) might not support it.
Note that a flexible array member must be the last member of a struct, and it cannot be the only member.
It's a replacement for the "struct hack", described in question 2.6 of the comp.lang.c FAQ.
You might want to use undimensioned arrays in systems with custom memory allocators where you have limited ammount of memory. Say, in embedded software.
If you have a struct defined as:
struct test
{
int a;
unsigned long int asd[][6][3];
};
and a memory pool:
int *b = new unsigned long int[1000];
you can 'allocate' memory and use the structure as follows:
test *t;
t = (test*)b;
t->asd[1][2][3] = 1;
This approach is more flexible than making an array of specified dimensions. Of course you can always use a structure like
struct test2
{
int a;
unsigned long int ***asd;
};
but you'll need to initialize the pointer separately and the pointer itself needs 4 bytes.
As a bonus of not using a pointer the structure is represented as a linear memory chunk and can be serialized as is.