undimensioned array field, its use? - c

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.

Related

Structure of a book in C

I am new to C programming and in the development of this exercise I encountered this error that I cannot resolve:
Fields must have a constant size: 'variable length array in structure' extension will never be supported
#include <stdio.h>
#include <stdlib.h>
int main(int argc, const char * argv[]) {
int nChapters = 2;
typedef struct {
char title[50];
char author[50];
} Heading;
typedef struct {
char title[50];
int number_pages;
} Chapter;
typedef struct {
Heading heading;
Chapter chapters[nChapters]; //Fields must have a constant size: 'variable length array in structure' extension will never be supported
} Book;
printf("\n");
system("read -p 'Press enter to continue...' ");
printf("Hello, World!\n");
return 0;
}
If I replace chapters[nChapters] with an int like chapters[2], program run without problems. Thanks in advance!
In C you have to declare arrays using a fixed length, your nChapters variable is indeed, a variable. You can turn it into a constant variable by simply adding the const keyword:
const int nChapters = 2
You can use the preprocessor directive #define:
#define nChapters 2
The issue is that you are assuming that it is obvious
Chapter chapters[nChapters];
that value of nChapters is 2.
It works that way for a array which is not within a struct or a union.
This is supported by weird, non-standard, non-GCC (but accepted as an extension by GCC in C90 onwards), not recommended feature called as VLA or Variable Length Arrays. Using these, one can allocate a auto class array.
Referring to GNU/GCC documentation, section 6.20, It is trivial to note that,
The storage is allocated at the point of declaration and deallocated when the block scope containing the declaration exits.
C99 recommends a better way to deal with this requirement - by using flexible length array.
§6.7.2.1 Structure and union specifiers
¶18 As a special case, the last element of a structure with more than one named member may have an incomplete array type; this is called a flexible array member. In most situations, the flexible array member is ignored. In particular, the size of the structure is as if the flexible array member were omitted except that it may have more trailing padding than the omission would imply. However, when a . (or ->) operator has a left operand that is (a pointer to) a structure with a flexible array member and the right operand names that member, it behaves as if that member were replaced with the longest array (with the same element type) that would not make the structure larger than the object being accessed; the offset of the array shall remain that of the flexible array member, even if this would differ from that of the replacement array. If this array would have no elements, it behaves as if it had one element but the behavior is undefined if any attempt is made to access that element or to generate a pointer one past it.
So, that would change your struct to:
typedef struct {
Heading heading;
Chapter chapters[];
} Book;
And then allocate the memory dynamically from heap - using malloc.
The size of the array member of the struct has to be a constant expression (skip "flexible member" case and GCC's VLA-in-struct extension).
In the C standard the only portable way to have a true named integer constant is using enums.
Just replace:
int nChapters = 2;
with this:
enum { nChapters = 2 };

C: Is accessing initial member of nested struct using pointer cast to "outer" struct type defined? [duplicate]

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.

Struct padding for single member structs

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).

Declare Global Struct Variable With Variable Size In C

I have a struct that looks like this.
struct MyStruct1 {
int (*fn)();
int b;
}
And another struct that looks like this.
struct MyStruct2 {
int a;
struct MyStruct1 b[0];
}
I would like to declare a global variable of type MyStruct2, somewhat like this.
int func1() { return 1; }
int func2() { return 2; }
struct MyStruct2 a = { 1, { func1, 5 }, { func2, 6 } };
However, I get a "Initializer Element is not a compile-time constant".
I would like to know (a) if it is even possible to globally declare a variable sized struct (or at least define a chunk of space, of the correct size, to have the values inserted into later), and (b) if it is, what am I doing wrong?
It is not possible to declare a variable-size struct, neither locally nor globally. Every type has a fixed size determined at compile time.
The error message you report is surprising, however. If all the declarations you gave are at file scope, in the same file, in the order you gave them, then the initializer for variable a is a compile-time constant. However, it is not a proper initializer for a struct MyStruct2,
because it specifies more elements than that struct type has members,
because the initializer element for a.b is an initializer for a struct MyStruct1 instead of for an array of such, and
because even if you converted the last two initializer elements into one array initializer, it has more elements than there are elements in a.b (i.e. more than zero).
If you want a dynamically-sized array, whether as a variable in its own right or as a member of a struct, then you must declare a pointer to it, and allocate memory for the elements dynamically. In that case, the elements are not themselves part of the struct; only the pointer to them is. (That is different, by the way, from a fixed size array whose size is implicit in its initializer; these are possible only for independent types, though, not for types of struct or union members).
EDIT:
C99 flexible arrays are a possible alternative, as ShafikYaghmour commented. These are similar, but not identical, to a struct element that is a pointer to a dynamically-allocated array. In that case, however, you not only cannot statically declare the array elements, you also cannot statically declare instances of the struct itself, so this wouldn't at all get around your initializer issue. There are several other quirks and limitations. Personally I see few advantages to flexible arrays, but they do make it a bit easier to properly free struct instances.
You cannot create arrays of size 0 in C legitimately. In C99 or C11, you can use a 'flexible array member' like this:
struct MyStruct2 {
int a;
struct MyStruct1 b[];
};
but structures that have a flexible array member can only usefully be created with dynamic memory allocation (other forms of allocation give you an unusable flexible array of size 0).
The older 'struct hack' version of a structure with a variable size array uses an array of size 1 in the structure. You can create global versions of such a structure with an array of size 1.
But basically, you are trying to do what the language prohibits you from doing, and not very surprisingly, you are failing.
What you do about this depends on what you need. Global variables are inherently somewhat undesirable, so there's an element of "you should be trying to avoid doing this". That said, the rules apply to file scope (static) variables too, and those have many uses.
You can use an explicit pointer in place of the array, and have separate allocations of the body of the struct MyStruct2 and its array of struct MyStruct1 members. You can forgo the global variable and use dynamically allocated structures with a flexible array member.
struct MyStruct2 *ms2 = malloc(sizeof(*ms2) + N * sizeof(ms2->b[0]));
This creates an struct MyStruct2 (as shown at the top of this answer) with N members in the array. Without any further changes, you can use ms2->b[0] through ms2->b[N-1] (well, apart from error checking that the malloc() succeeded).

Representing dynamic typing in C

I'm writing a dynamically-typed language. Currently, my objects are represented in this way:
struct Class { struct Class* class; struct Object* (*get)(struct Object*,struct Object*); };
struct Integer { struct Class* class; int value; };
struct Object { struct Class* class; };
struct String { struct Class* class; size_t length; char* characters; };
The goal is that I should be able to pass everything around as a struct Object* and then discover the type of the object by comparing the class attribute. For example, to cast an integer for use I would simply do the following (assume that integer is of type struct Class*):
struct Object* foo = bar();
// increment foo
if(foo->class == integer)
((struct Integer*)foo)->value++;
else
handleTypeError();
The problem is that, as far as I know, the C standard makes no promises about how structures are stored. On my platform this works. But on another platform struct String might store value before class and when I accessed foo->class in the above I would actually be accessing foo->value, which is obviously bad. Portability is a big goal here.
There are alternatives to this approach:
struct Object
{
struct Class* class;
union Value
{
struct Class c;
int i;
struct String s;
} value;
};
The problem here is that the union uses up as much space as the size of the largest thing that can be stored in the union. Given that some of my types are many times as large as my other types, this would mean that my small types (int) would take up as much space as my large types (map) which is an unacceptable tradeoff.
struct Object
{
struct Class* class;
void* value;
};
This creates a level of redirection that will slow things down. Speed is a goal here.
The final alternative is to pass around void*s and manage the internals of the structure myself. For example, to implement the type test mentioned above:
void* foo = bar();
// increment foo
if(*((struct Class*) foo) == integer)
(*((int*)(foo + sizeof(struct Class*))))++;
else
handleTypeError();
This gives me everything I want (portability, different sizes for different types, etc.) but has at least two downsides:
Hideous, error-prone C. The code above only calculates a single-member offset; it will get much worse with types more complex than integers. I might be able to alleviate this a bit using macros, but this will be painful no matter what.
Since there is no struct that represents the object, I don't have the option of stack allocations (at least without implementing my own stack on the heap).
Basically, my question is, how can I get what I want without paying for it? Is there a way to be portable, have variance in size for different types, not use redirection, and keep my code pretty?
EDIT: This is the best response I've ever received for an SO question. Choosing an answer was hard. SO only allows me to choose one answer so I chose the one that lead me to my solution, but you all received upvotes.
See Python PEP 3123 (http://www.python.org/dev/peps/pep-3123/) for how Python solves this problem using standard C. The Python solution can be directly applied to your problem. Essentially you want to do this:
struct Object { struct Class* class; };
struct Integer { struct Object object; int value; };
struct String { struct Object object; size_t length; char* characters; };
You can safely cast Integer* to Object*, and Object* to Integer* if you know that your object is an integer.
C gives you sufficient guarantees that your first approach will work. The only modification you need to make is that in order to make the pointer aliasing OK, you must have a union in scope that contains all of the structs that you are casting between:
union allow_aliasing {
struct Class class;
struct Object object;
struct Integer integer;
struct String string;
};
(You don't need to ever use the union for anything - it just has to be in scope)
I believe the relevant part of the standard is this:
[#5] With one exception, if the value
of a member of a union object is used
when the most recent store to the
object was to a different member, the
behavior is implementation-defined.
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.
(This doesn't directly say it's OK, but I believe that it does guarantee that if two structs have a common intial sequence and are put into a union together, they'll be laid out in memory the same way - it's certainly been idiomatic C for a long time to assume this, anyway).
There are 3 major approaches for implementing dynamic types and which one is best depends on the situation.
1) C-style inheritance: The first one is shown in Josh Haberman's answer. We create a type-hierarchy using classic C-style inheritance:
struct Object { struct Class* class; };
struct Integer { struct Object object; int value; };
struct String { struct Object object; size_t length; char* characters; };
Functions with dynamically typed arguments receive them as Object*, inspect the class member, and cast as appropriate. The cost to check the type is two pointer hops. The cost to get the underlying value is one pointer hop. In approaches like this one, objects are typically allocated on the heap since the size of objects is unknown at compile time. Since most `malloc implementations allocate a minimum of 32 bytes at a time, small objects can waste a significant amount of memory with this approach.
2) Tagged union: We can remove a level of indirection for accessing small objects using the "short string optimization"/"small object optimization":
struct Object {
struct Class* class;
union {
// fundamental C types or other small types of interest
bool as_bool;
int as_int;
// [...]
// object pointer for large types (or actual pointer values)
void* as_ptr;
};
};
Functions with dynamically typed arguments receive them as Object, inspect the class member, and read the union as appropriate. The cost to check the type is one pointer hop. If the type is one of the special small types, it is stored directly in the union, and there is no indirection to retrieve the value. Otherwise, one pointer hop is required to retrieve the value. This approach can sometimes avoid allocating objects on the heap. Although the exact size of an object still isn't known at compile time, we now know the size and alignment (our union) needed to accommodate small objects.
In these first two solutions, if we know all the possible types at compile time, we can encode the type using an integer type instead of a pointer and reduce type check indirection by one pointer hop.
3) Nan-boxing: Finally, there's nan-boxing where every object handle is only 64 bits.
double object;
Any value corresponding to a non-NaN double is understood to simply be a double. All other object handles are a NaN. There are actually large swaths of bit values of double precision floats that correspond to NaN in the commonly used IEEE-754 floating point standard. In the space of NaNs, we use a few bits to tag types and the remaining bits for data. By taking advantage of the fact that most 64-bit machines actually only have a 48-bit address space, we can even stash pointers in NaNs. This method incurs no indirection or extra memory use but constrains our small object types, is awkward, and in theory is not portable C.
Section 6.2.5 of ISO 9899:1999 (the C99 standard) says:
A structure type describes a sequentially allocated nonempty set of member objects (and, in certain circumstances, an incomplete array), each of which has an optionally specified name and possibly distinct type.
Section 6.7.2.1 also says:
As discussed in 6.2.5, a structure is a type consisting of a sequence of members, whose storage is allocated in an ordered sequence, and a union is a type consisting of a sequence of members whose storage overlap.
[...]
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.
This guarantees what you need.
In the question you say:
The problem is that, as far as I know, the C standard makes no promises about how structures are stored. On my platform this works.
This will work on all platforms. It also means that your first alternative - what you are currently using - is safe enough.
But on another platform struct StringInteger might store value before class and when I accessed foo->class in the above I would actually be accessing foo->value, which is obviously bad. Portability is a big goal here.
No compliant compiler is allowed to do that. [I replaced String by Integer assuming you were referring to the first set of declarations. On closer examination, you might have been referring to the structure with an embedded union. The compiler still isn't allowed to reorder class and value.]
The problem is that, as far as I know, the C standard makes no promises about how structures are stored. On my platform this works. But on another platform struct String might store value before class and when I accessed foo->class in the above I would actually be accessing foo->value, which is obviously bad. Portability is a big goal here.
I believe you're wrong here. First, because your struct String doesn't have a value member. Second, because I believe C does guarantee the layout in memory of your struct's members. That's why the following are different sizes:
struct {
short a;
char b;
char c;
}
struct {
char a;
short b;
char c;
}
If C made no guarantees, then compilers would probably optimize both of those to be the same size. But it guarantees the internal layout of your structs, so the natural alignment rules kick in and make the second one larger than the first.
I appreciate the pedantic issues raised by this question and answers, but I just wanted to mention that CPython has used similar tricks "more or less forever" and it's been working for decades across a huge variety of C compilers. Specifically, see object.h, macros like PyObject_HEAD, structs like PyObject: all kinds of Python Objects (down at the C API level) are getting pointers to them forever cast back and forth to/from PyObject* with no harm done. It's been a while since I last played sea lawyer with an ISO C Standard, to the point that I don't have a copy handy (!), but I do believe that there are some constraints there that should make this keep working as it has for nearly 20 years...

Resources