Structs and Unions
Structs are a great way to pair multiple variables together and allow them to be passed as a group. Unions are very similar to structs, but the data shares the same memory spaces, so they cannot be used at the same time.
Structs
Struct Declaration
A struct is a type consisting of a sequence of members whose storage is allocated in an ordered sequence (as opposed to union, which is a type consisting of a sequence of members whose storage overlaps).
The type specifier for a struct is identical to the union
type specifier except for the keyword used:
Syntax
struct attr-spec-seq(optional) name(optional) { struct-declaration-list
} |
(1) |
struct attr-spec-seq(optional) name |
(2) |
1) Struct definition: introduces the new type struct name and defines its meaning
2) If used on a line of its own, as in struct
name ;
,
declares but doesn't define the struct name
(see forward declaration below). In other contexts, names the previously-declared struct, and attr-spec-seq is not allowed.
name | - | the name of the struct that's being defined |
struct-declaration-list | - | any number of variable declarations, bit field declarations, and static assert declarations. Members of incomplete type and members of function type are not allowed (except for the flexible array member described below) |
attr-spec-seq | - | (C23)optional list of attributes, applied to the struct type |
Explanation
Within a struct object, addresses of its elements (and the addresses of the bit field allocation units) increase in order in which the members were defined. A pointer to a struct can be cast to a pointer to its first member (or, if the member is a bit field, to its allocation unit). Likewise, a pointer to the first member of a struct can be cast to a pointer to the enclosing struct. There may be unnamed padding between any two members of a struct or after the last member, but not before the first member. The size of a struct is at least as large as the sum of the sizes of its members.
If a struct defines at least one named member, it is allowed to additionally declare its last member with incomplete array type. When an element of the flexible array member is accessed (in an expression that uses operator
struct s { int n; double d[]; }; // s.d is a flexible array member |
(since C99) |
Similar to union, an unnamed member of a struct whose type is a struct without name is known as anonymous struct. Every member of an anonymous struct is considered to be a member of the enclosing struct or union. This applies recursively if the enclosing struct or union is also anonymous. struct v {
Similar to union, the behavior of the program is undefined if struct is defined without any named members (including those obtained via anonymous nested structs or unions). |
(since C11) |
Forward declaration
A declaration of the following form
struct attr-spec-seq(optional) name ; |
hides any previously declared meaning for the name name in the tag name space and declares name as a new struct name in current scope, which will be defined later. Until the definition appears, this struct name has
incomplete type.
This allows structs that refer to each other:
struct y; struct x {struct y *p;/* ... */}; struct y {struct x *q;/* ... */};
Note that a new struct name may also be introduced just by using a struct tag within another declaration, but if a previously declared struct with the same name exists in the tag name space, the tag would refer to that name
struct s* p = NULL; // tag naming an unknown struct declares it
struct s { int a; }; // definition for the struct pointed to by p
void g(void)
{
struct s; // forward declaration of a new, local struct s
// this hides global struct s until the end of this block
struct s *p; // pointer to local struct s
// without the forward declaration above,
// this would point at the file-scope s
struct s { char* p; }; // definitions of the local struct s
}
Keywords
struct
Notes
Because members of incomplete type are not allowed, and a struct type is not complete until the end of the definition, a struct cannot have a member of its own type. A pointer to its own type is allowed, and is commonly used to implement nodes in linked lists or trees.
Because a struct declaration does not establish scope, nested types, enumerations and enumerators introduced by declarations within struct-declaration-list are visible in the surrounding scope where the struct is defined.
Example
#include <stddef.h>
#include <stdio.h>
int main(void)
{
struct car { char *make; char *model; int year; }; // declares the struct type
// declares and initializes an object of a previously-declared struct type
struct car c = {.year=1923, .make="Nash", .model="48 Sports Touring Car"};
printf("car: %d %s %s\n", c.year, c.make, c.model);
// declares a struct type, an object of that type, and a pointer to it
struct spaceship { char *make; char *model; char *year; }
ship = {"Incom Corporation", "T-65 X-wing starfighter", "128 ABY"},
*pship = &ship;
printf("spaceship: %s %s %s\n", ship.year, ship.make, ship.model);
// addresses increase in order of definition
// padding may be inserted
struct A { char a; double b; char c;};
printf("offset of char a = %zu\noffset of double b = %zu\noffset of char c = %zu\n"
"sizeof(struct A) = %zu\n", offsetof(struct A, a), offsetof(struct A, b),
offsetof(struct A, c), sizeof(struct A));
struct B { char a; char b; double c;};
printf("offset of char a = %zu\noffset of char b = %zu\noffset of double c = %zu\n"
"sizeof(struct B) = %zu\n", offsetof(struct B, a), offsetof(struct B, b),
offsetof(struct B, c), sizeof(struct B));
// A pointer to a struct can be cast to a pointer to its first member and vice versa
char* pmake = (char*)pship;
pship = (struct spaceship *)pmake;
}
Possible output:
car: 1923 Nash 48 Sports Touring Car spaceship: 128 ABY Incom Corporation T-65 X-wing starfighter offset of char a = 0 offset of double b = 8 offset of char c = 16 sizeof(struct A) = 24 offset of char a = 0 offset of char b = 1 offset of double c = 8 sizeof(struct B) = 16
Unions
Unions are similar to structures but only one member within the union can be used at a time, due to it having a shared memory size for all members.
union identifier {
char desc[50] ;
float price ;
int devs ;
} ;
Unions are used when just one condition will be applied and only one variable is required.
Code
#include <iostream>
using namespace std ;
union opSysName {
char desc[50];
float price;
int devs;
} ;
int main() {
union opSysName linux = {"fun, free and better!"} ;
cout << "The best OS might just possibly be " << linux.desc ;
/*set price - remembering that a union can only hold one variable at a time*/
linux.price = 0.01 ;
cout << " It costs less than $" << linux.price ;
linux.devs = 1200 ;
cout << " and has about " << linux.devs << " developers on each release!" << endl << endl ;
union opSysName windows = {"restrictive, proprietary and monopolistic!"} ;
cout << "A certain other consumer OS is generally " << windows.desc ;
windows.price = 99.99 ;
cout << " It costs too much, i.e £" << windows.price ;
windows.devs = 1138 ;
cout << ", and has around " << windows.devs << " developers trying to make it work!" ;
return 0;
}
Output:
The best OS might just possibly be fun, free and better! It costs less than $0.01 and has about 1200 developers on each release! |
Source: Derrick Robinson, http://cpp.tech-academy.co.uk/unions/ This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 License.
Source: C++ Reference, https://en.cppreference.com/w/c/language/struct
This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License.