Hub Nexus
Updated

AuthorNo author yetClaim it

See something to improve? Propose a change.

Support

In the chapter Variables we looked at the primitive data types. However, more advanced data types allow us greater flexibility in managing data in our program.

Structs

Structs are data types made of variables of other data types (possibly including other structs). They are used to group pieces of information into meaningful units, and also permit some constructs not possible otherwise. The variables declared in a struct are called members.

Defining

One defines a struct using the struct keyword and a block of members. These members are specified using variable declarations. For example:

struct mystruct {
int int_member;
double double_member;
char string_member[25];
} struct_var;

struct_var is a variable of type struct mystruct, which we declared along with the definition of the new struct mystruct data type.

More commonly, struct variables are declared after the definition of the struct, using the form:

struct mystruct {
// ...
};

struct mystruct struct_var;

Accessing members

The members of a struct variable may be accessed using the member access operator . (a dot):

struct_var.int_member = 0;

Type synonyms

It is often common practice to make a type synonym so we don't have to type "struct mystruct" all the time. C allows us the possibility to do so using a typedef statement, which aliases a type:

typedef struct {
// ...
} Mystruct;

The struct itself is an incomplete type (by the absence of a name on the first line), but it is aliased as Mystruct. Then the following may be used:

Mystruct struct_var;

Nesting

Structs may contain not only their own variables but may also contain other structs:

Self Referencing Structs

Structs cannot contain instances of themselves but they can have pointers to themselves. Such structs are often called Self-referential Structs. Linked lists are an example of self-referential structs. Here's how you can declare a linked list in C.

struct Node {
int data; // data held at current Node
struct Node* next; // pointer to next node
};

However the following is not allowed:

typedef struct {
int data;
Node *next; // invalid
} Node;

Since Node is not yet defined inside the struct, the compiler will throw an error.

typedef struct Node {
int data;
struct Node *next; // valid
} Node;

Members and memory

If our new type really is a type, then like any other type, it must have a size.

Size

Recall struct mystruct from earlier. It is composed of an int, double, and char[25]. On most modern systems, these have sizes of 4, 8, and 25 bytes, respectively. What do you think is the size of struct mystruct as a whole? Would it be {}4 + 8 + 25 = 37 bytes?

Let's test this assumption.

The whole is greater than the sum of its parts! Why is this? It has to do with alignment, which we'll cover now.

Alignment

If a two-byte short is placed in memory at address 0080, another one couldn't be placed at 0079 or 0081 since it will overlap; 0078 and 0082 are the next closest options, leaving no gap between them in memory. However, if there was a one-byte char at 0082, a following second short wouldn't be allocated at 0083, but at 0084, leaving one byte of padding in the middle.

This is because your processor is a little picky in how it wants to load data from and store data to memory. This pickiness is related to your processor's word size, which is 64 bits (or 8 bytes) on most computers today. There might be a performance penalty if either of the following happen:

  • the load address isn't a multiple of the load size
  • the load size isn't a power of two (in bytes) including one, capped at the word size

Compiled programs must have the processor either access members of the new type directly or split a load or store of the whole thing into multiple word-size loads. And, on some systems, the above scenarios are outright prohibited. To avoid any performance penalties (at best) or crashes (at worst), all types in C have an alignment, which is the number of bytes that must be between each instance of that type. When data is allocated, unused padding bytes fill space in memory before it until the alignment is met. All primitive types have sizes that are powers of two, so their alignments are always their sizes or the word size, whichever is smaller. Structs, being made of multiple members of possibly different sizes, have the alignment of their largest-alignment member.

The alignof keyword in the above example works like sizeof. It is not a function; during compilation, it evaluates to the alignment of the type or object it is given. Using the values from the example, we can recreate how our data is laid out in memory:

OffsetValue
004 bytes for int_member
044 bytes of padding, to align double_member
088 bytes for double_member
0C
1025 bytes for string_member
14
18
1C
20
24
287 bytes of padding, to align the struct itself
2C

Repacking

If we rearrange the struct so that the members are ordered from greatest alignment to least alignment, the padding hole is removed from the middle of the struct.

Swapping the first two members:

struct mystruct {
double double_member;
int int_member;
char string_member[25];
};

...results in sizeof(struct mystruct) == 40, saving eight bytes! Let's check its new memory layout:

OffsetValue
008 bytes for double_member
04
084 bytes for int_member
0C25 bytes for string_member
10
14
18
1C
20
247 bytes of padding, to align the struct itself
28

Now, the only unused space is the unavoidable alignment padding at the end of the struct.

Enumerations

Enumerations are artificial data types representing associations between labels and integers. Unlike structs or unions, they are not composed of other data types. An example declaration:

enum weather {
sunny,
windy,
cloudy,
rain,
} weather_outside;

In the example above, sunny equals 0, windy equals 1, ... and so on. It is possible to assign values to labels within the integer range, but they must be a literal.

Switching on value

Similar declaration syntax that applies for structs and unions also applies for enums. Also, one normally doesn't need to be concerned with the integers that labels represent:

enum weather weather_outside = rain;

This peculiar property makes enums especially convenient in switch-case statements:

switch (weather_outside) {
case sunny:
wear_sunglasses();
break;
case windy:
wear_windbreaker();
break;
case cloudy:
get_umbrella();
break;
case rain:
get_umbrella();
wear_raincoat();
break;
}

Changing data type

Sometimes, the underlying data type for the enumeration matters. Since C23, it is possible to set the exact type of integer to use:[1]

enum weather : short {
sunny,
windy,
cloudy,
rain,
} weather_outside;

Unions

The definition of a union is similar to that of a struct. The difference between the two is that in a struct, the members occupy different areas of memory, but in a union, the members occupy the same area of memory. Thus, in the following type, for example:

union {
int i;
double d;
} u;

The size of a union is the size of its largest member.

Tagged union

Imagine that you are developing a settings editor for an application. In this application, setting values can be integers, floating-point numbers, or single characters. Despite this, it's possible to represent all setting values with a single type.

Structs, enums, and unions can be combined to create a tagged union, a complex type which pairs some data that varies in type with information on the current type of that data. The C runtime does not keep track of the types of data in your program, so an enum is used to define all types that data could be. A union represents the varying-type data, and a struct keeps the type enum and data union together. Combining this with type synonyms, we get the following:

References

Where this page came from

This page was imported from Wikibooks. From “C programming” on Wikibooks, by its contributors, under CC BY-SA 4.0. Changed here: set as a page; navigation and edit links left out; each image under its own licence, credited in its caption.

Nobody has written it yet — it is the source material at a new address, which is why search engines are asked to skip it and why no one earns from it. It is up for grabs: take it on, and it is yours to rewrite and to earn from.

In these publicationsC programming

LanguagesEnglish

Licence: CC BY-SA 4.0 · Adapted from en.wikibooks.org

1

0

0

0

Spinner Logo

Comments

Spinner Logo
Version: 2CC0 1.0 — public domain
The runaway star that left the Tarantula Nebula
Version: 2CC0 1.0 — public domain
The Blackwell School, where segregation had no law behind it
Version: 2CC0 1.0 — public domain
The Eagle Nebula, seen in the infrared
Version: 2CC0 1.0 — public domain
The house where the Equal Rights Amendment was written
Version: 2CC0 1.0 — public domain
The Aleutians, the forgotten front of the Second World War
Version: 2CC0 1.0 — public domain
The Cosmic Cliffs are not cliffs