Home | Projects | Notes > C Programming > Structures
Structures in C is a data structure used to create user-defined data types.
Structures allow us to combine data of different types
Defining a structure:
xxxxxxxxxx
71struct tag_name
2{
3 member_element1;
4 member_element2;
5 member_element3;
6 member_element4;
7}; /* don't forget this semi-colon! */
struct
is a reserved keyword in C.
Structure definition example:
xxxxxxxxxx
71struct CarModel
2{
3 unsigned int carNumber;
4 uint32_t carPrice;
5 uint16_t carMaxSpeed;
6 float carWeight;
7};
Defining a structure does not incur memory allocation. Its just a description or a record.
Creating structure variables:
xxxxxxxxxx
11struct CarModel CarBMW, CarFord, CarHonda; /* memory allocation takes place at this point */
struct CarModel
: User-defined data type
CarBMW
,CarFord
,CarHonda
: Structure variables
Initializing the structure member variables:
xxxxxxxxxx
81/* C89 method (order is important!) */
2struct CarModel CarBMW = {2021, 15000, 220, 1330};
3
4/* C99 method using designated initializers (order is NOT important!)) */
5struct CarModel CarBMW = {.carNumber = 2021,
6 .carWeight = 1330,
7 .carMaxSpeed = 220,
8 .carPrice = 15000};
Accessing the structure member variables:
xxxxxxxxxx
21/* use .(dot operator) to access the member variables */
2printf("%d\n", CarBMW.carPrice); /* 15000 */
Write a program to create a carModel
structure and create 2 variables of type struct carModel
. Initialize the variables with the below given data and then print them.
2021, 15000, 220, 1330
4031, 35000, 160, 1900.96
xxxxxxxxxx
331
2
3
4struct carModel
5{
6 uint32_t carNumber;
7 uint32_t carPrice;
8 uint16_t carMaxSpeed;
9 float carWeight;
10}; /* don't forget this semi-colon! */
11
12int main(int argc, char *argv[])
13{
14 struct carModel carBMW = {2021, 15000, 220, 1330};
15 struct carModel carFord = {.carNumber = 4031, .carWeight = 1900.96,
16 .carMaxSpeed = 160, .carPrice = 35000};
17
18 printf("Car BMW information:\n");
19 printf("carNumber = %u\n", carBMW.carNumber);
20 printf("carPrice = %u\n", carBMW.carPrice);
21 printf("carMaxSpeed = %u\n", carBMW.carMaxSpeed);
22 printf("carWeight = %f\n", carBMW.carWeight);
23
24 puts("");
25
26 printf("Car Ford information:\n");
27 printf("carNumber = %u\n", carFord.carNumber);
28 printf("carPrice = %u\n", carFord.carPrice);
29 printf("carMaxSpeed = %u\n", carFord.carMaxSpeed);
30 printf("carWeight = %f\n", carFord.carWeight);
31
32 return 0;
33}
xxxxxxxxxx
111Car BMW information:
2carNumber = 2021
3carPrice = 15000
4carMaxSpeed = 220
5carWeight = 1330.000000
6
7Car Ford information:
8carNumber = 4031
9carPrice = 35000
10carMaxSpeed = 160
11carWeight = 1900.959961
Do not prematurely assume that the size of the structure is going to be all the member variables' sizes put together. See the following example:
xxxxxxxxxx
201
2
3
4struct carModel
5{
6 uint32_t carNumber; /* 4 byte */
7 uint32_t carPrice; /* 4 byte */
8 uint16_t carMaxSpeed; /* 2 byte */
9 float carWeight; /* 4 byte */
10}; /* don't forget this semi-colon! */
11
12int main(int argc, char *argv[])
13{
14 struct carModel carBMW = {2021, 15000, 220, 1330};
15
16 printf("sizeof(struct carModel) = sizeof(carBMW) = %lu\n", sizeof(struct carModel));
17 /* is it going to be 4+4+2+4=14 bytes? */
18
19 return 0;
20}
xxxxxxxxxx
11izeof(struct carModel) = sizeof(carBMW) = 16
%lu
in line 16 specifies "long unsigned int".Note that the return type of
sizeof()
issize_t
, which is platform dependent.
This can be explained by aligned/unaligned data storage.
For efficiency, the compiler generates instructions to store variables on their natural size boundary addresses in the memory. This is also true for structures. Fields in a structure are located on their natural size boundary.
Natural size boundary:
xxxxxxxxxx
91 char (1-byte aligned)
2Address: 0403010 0403011 0403012 0403013 0403014 0403015 ...
3 - - - - - -
4 short (2-byte aligned)
5Address: 0403010 0403012 0403014 0403016 0403016 0403018 ...
6 - - - - - -
7 int (4-byte aligned)
8Address: 0403010 0403014 0403018 040301C 0403020 0403024 ...
9 - - - - - -
The following program demonstrates what happens when the compiler stores the variables according to their natural size boundary. Empty memory space between member variables are padded with 0.
xxxxxxxxxx
341
2
3
4struct DataSet
5{
6 char data1; /* 1 byte */
7 int data2; /* 4 byte */
8 char data3; /* 1 byte */
9 short data4; /* 2 byte */
10};
11
12int main(int argc, char *argv[])
13{
14 struct DataSet data;
15
16 data.data1 = 0x11;
17 data.data2 = 0xFFFFEEEE;
18 data.data3 = 0x22;
19 data.data4 = 0xABCD;
20
21 uint8_t *ptr = (uint8_t*)&data;
22
23 uint32_t totalSize = sizeof(struct DataSet);
24
25 for (uint32_t i = 0; i < totalSize; i++)
26 {
27 printf("%p %X\n", ptr, *ptr);
28 ptr++;
29 }
30
31 printf("Total memory consumed by this struct variable = %lu\n", sizeof(struct DataSet));
32
33 return 0;
34}
xxxxxxxxxx
1310x7ffd2ecbcb6c 11 <- start of data1 (also the base address of the structure variable data)
20x7ffd2ecbcb6d 0 (padding)
30x7ffd2ecbcb6e 0 (padding)
40x7ffd2ecbcb6f 0 (padding)
50x7ffd2ecbcb70 EE <- start of data2 (can only be assigned to a word-aligned address)
60x7ffd2ecbcb71 EE
70x7ffd2ecbcb72 FF
80x7ffd2ecbcb73 FF
90x7ffd2ecbcb74 22 <- start of data3
100x7ffd2ecbcb75 0 (padding)
110x7ffd2ecbcb76 CD <- start of data4 (can only be assigned to a half-word-aligned address)
120x7ffd2ecbcb77 AB
13Total memory consumed by this struct variable = 12
Due to the aligned data storage, the size of a structure variable is generally bigger than the sum of the size of each member variable.
Why does the compiler do this?
When the data is stored in aligned fashion it becomes a lot easier for the processor to do the read/write transactions on the memory. (Processor performance increases)
Unaligned data storage will generate more instructions during the compilation process to deal with that unalignment which will actually increase the code size. To test this, write a test program to compare packed vs. non-packed structures and inspect the generated assembly code (disassembly).
The only negative side effect of aligned data storage is that you will lose some memory space due to padding. If you can't afford to lose any memory space, you could go with unaligned data storage. Using the gcc attribute packed, the compiler will generate packed structures which results in unaligned data storage.
xxxxxxxxxx
71struct data
2{
3 char data1;
4 int data2;
5 char data3;
6 short data4;
7}__attribute__((packed)); /* sizeof(struct data) = 8 bytes
typedef
with Structurestypedef
is used to give an alias to primitive and user defined data types.
xxxxxxxxxx
101struct CarModel
2{
3 uint32_t carNumber;
4 uint32_t carPrice;
5 uint16_t carMaxSpeed;
6 float carWeight;
7};
8
9/* in main() */
10struct CarModel carBMW, carFord;
xxxxxxxxxx
101typedef struct /* tag name is optional */
2{
3 uint32_t carNumber;
4 uint32_t carPrice;
5 uint16_t carMaxSpeed;
6 float carWeight;
7} CarModel_t; /* but give an alias to this structure */
8
9/* in main() */
10CarModel_t carBMW, carFord;
By convention, the suffix
_t
is used for typedef defined data types,_e
is used for enum defined data types.
A structure type cannot contain itself as a member.
xxxxxxxxxx
81struct CarModel
2{
3 uint32_t carNumber;
4 uint32_t carPrice;
5 uint16_t carMaxSpeed;
6 float carWeight;
7 //struct CarModel carBMW; // not allowed
8};
However, structure types can contain pointers to their own types. Such self-referential structures are used in implementing linked lists and binary trees, etc.
xxxxxxxxxx
81struct CarModel
2{
3 uint32_t carNumber;
4 uint32_t carPrice;
5 uint16_t carMaxSpeed;
6 float carWeight;
7 struct CarModel *pcarBMW; // allowed
8};
Nest structure means "structure inside a structure".
xxxxxxxxxx
121struct Data
2{
3 char data1;
4 int data2;
5 char data3;
6 short data4;
7 struct
8 {
9 char data5;
10 int data6;
11 } moreData; /* this is neither a tag name nor a typedef name; it is just a variable name */
12}
xxxxxxxxxx
131struct CarModel
2{
3 uint32_t carNumber;
4 uint32_t carPrice;
5 uint16_t carMaxSpeed;
6 float carWeight;
7 struct
8 {
9 float temperature;
10 float airPressure;
11 int fuel;
12 } carParameters;
13}
Given the structure variable's name, use the .
(dot operator) to access the member variables:
xxxxxxxxxx
11CarBMW.carPrice = 1000;
Given the structure variable's address (or pointer), use the ->
(arrow operator) to access the member variables:
xxxxxxxxxx
21CarModel *pCarBMW = &CarBMW;
2pCarBMW->carPrice = 1000; /* equivalent to *(address of the member 'carPrice') = 1000 */
The ->
operator is also called as "structure pointer dereference operator", or "class member access operator", etc.
Write a program to decode a given 32-bit packet information and print the values of different fields. Create a structure with member elements as packet fields as shown below:
Solution:
xxxxxxxxxx
451
2
3
4struct Packet
5{
6 uint8_t crc;
7 uint8_t status;
8 uint16_t payload;
9 uint8_t bat;
10 uint8_t sensor;
11 uint8_t longAddr;
12 uint8_t shortAddr;
13 uint8_t addrMode;
14};
15
16int main(int argc, char *argv[])
17{
18 struct Packet packet;
19 uint32_t packetInfo;
20
21 printf("Enter 32-bit packet information (in hex): "); /* 0xFFFFFFFF or FFFFFFFF */
22 scanf("%x", &packetInfo);
23
24 packet.crc = (uint8_t)(packetInfo & 0x3);
25 packet.status = (uint8_t)((packetInfo >> 2) & 0x1);
26 packet.payload = (uint16_t)((packetInfo >> 3) & 0xFFF);
27 packet.bat = (uint8_t)((packetInfo >> 15) & 0x7);
28 packet.sensor = (uint8_t)((packetInfo >> 18) & 0x7);
29 packet.longAddr = (uint8_t)((packetInfo >> 21) & 0xFF);
30 packet.shortAddr = (uint8_t)((packetInfo >> 29) & 0x3);
31 packet.addrMode = (uint8_t)((packetInfo >> 31) & 0x1);
32
33 printf("crc : %#x\n", packet.crc);
34 printf("status : %#x\n", packet.status);
35 printf("payload : %#x\n", packet.payload);
36 printf("bat : %#x\n", packet.bat);
37 printf("sensor : %#x\n", packet.sensor);
38 printf("longAddr : %#x\n", packet.longAddr);
39 printf("shortAddr : %#x\n", packet.shortAddr);
40 printf("addrMode : %#x\n", packet.addrMode);
41
42 printf("Size of packet (struct) is %lu bytes.\n", sizeof(struct Packet)); /* %I64u */
43
44 return 0;
45}
xxxxxxxxxx
101Enter 32-bit packet information (in hex): 0xFFFFFFFF
2crc : 0x3
3status : 0x1
4payload : 0xfff
5bat : 0x7
6sensor : 0x7
7longAddr : 0xff
8shortAddr : 0x3
9addrMode : 0x1
10Size of packet (struct) is 10 bytes.
Notice that, with this approach, 10 bytes have been consumed to store 4-byte packet information. Is there any way we can do this without wasting memory space? Yes, by using bit fields.
Rewrite the previous exercise using structure bit fields.
xxxxxxxxxx
451
2
3
4struct Packet
5{
6 uint32_t crc :2;
7 uint32_t status :1;
8 uint32_t payload :12;
9 uint32_t bat :3;
10 uint32_t sensor :3;
11 uint32_t longAddr :8;
12 uint32_t shortAddr :2;
13 uint32_t addrMode :1;
14};
15
16int main(int argc, char *argv[])
17{
18 struct Packet packet;
19 uint32_t packetInfo;
20
21 printf("Enter 32-bit packet information (in hex): "); /* 0xFFFFFFFF or FFFFFFFF */
22 scanf("%x", &packetInfo);
23
24 packet.crc = (uint8_t)(packetInfo & 0x3);
25 packet.status = (uint8_t)((packetInfo >> 2) & 0x1);
26 packet.payload = (uint16_t)((packetInfo >> 3) & 0xFFF);
27 packet.bat = (uint8_t)((packetInfo >> 15) & 0x7);
28 packet.sensor = (uint8_t)((packetInfo >> 18) & 0x7);
29 packet.longAddr = (uint8_t)((packetInfo >> 21) & 0xFF);
30 packet.shortAddr = (uint8_t)((packetInfo >> 29) & 0x3);
31 packet.addrMode = (uint8_t)((packetInfo >> 31) & 0x1);
32
33 printf("crc : %#x\n", packet.crc);
34 printf("status : %#x\n", packet.status);
35 printf("payload : %#x\n", packet.payload);
36 printf("bat : %#x\n", packet.bat);
37 printf("sensor : %#x\n", packet.sensor);
38 printf("longAddr : %#x\n", packet.longAddr);
39 printf("shortAddr : %#x\n", packet.shortAddr);
40 printf("addrMode : %#x\n", packet.addrMode);
41
42 printf("Size of packet (struct) is %lu\n", sizeof(packet)); /* %I64u */
43
44 return 0;
45}
xxxxxxxxxx
101Enter 32-bit packet information (in hex): 0xFFFFFFFF
2crc : 0x3
3status : 0x1
4payload : 0xfff
5bat : 0x7
6sensor : 0x7
7longAddr : 0xff
8shortAddr : 0x3
9addrMode : 0x1
10Size of packet (struct) is 4 bytes.
Now the structure packet
consumes only 4 bytes.
Bit fields are widely used in network applications where extracting fields out of a Protocol Data Unit (PDU) (e.g., IP packet) is important.
Note that there can't be duplicate names for the bit fields of a structure.
Create a structure named CarDetails
which comprises below information:
Car max speed: Max 400km/h (7 bits)
Car weight in kg: Max 5000kg (13 bits)
Car color: an ACII code of color (7 bits)
Car price in USD: Max 100,000,000 (28 bits)
Structure without bit fields:
xxxxxxxxxx
71Struct CarDetails
2{
3 uint16_t speed;
4 uint16_t weight;
5 char colour;
6 uint32_t price;
7}; /* 4 + 4 + 4 = 12 bytes */
Structure using bit fields:
xxxxxxxxxx
91Struct CarDetails
2{
3 /* 4 bytes */
4 uint32_t speed : 7;
5 uint32_t weight : 13;
6 uint32_t colour : 7;
7 /* another 4 bytes */
8 uint32_t price;
9}; /* 4 + 4 = 8 bytes */
Using structures and bit fields, we can provide abstraction of the complicated code.
typedef
a structure for the register we want to bit-manipulate:
xxxxxxxxxx
211/* main.h */
2
3typedef struct
4{
5 uint32_t pin_0 :2;
6 uint32_t pin_1 :2;
7 uint32_t pin_2 :2;
8 uint32_t pin_3 :2;
9 uint32_t pin_4 :2;
10 uint32_t pin_5 :2;
11 uint32_t pin_6 :2;
12 uint32_t pin_7 :2;
13 uint32_t pin_8 :2;
14 uint32_t pin_9 :2;
15 uint32_t pin_10 :2;
16 uint32_t pin_11 :2;
17 uint32_t pin_12 :2;
18 uint32_t pin_13 :2;
19 uint32_t pin_14 :2;
20 uint32_t pin_15 :2;
21} GPIOx_MODER_t;
xxxxxxxxxx
61/* main.c */
2
3GPIOx_MODER_t volatile *const pGpioDMode;
4pGpioDMode = (GPIOx_MODE_t *)0x40020C00;
5
6pGpioDMode->pin_15 = 3; // set MODER15 to 11(2)
The compiler will generate the instructions to program the appropriate bit positions in the peripheral register address.
Encountering L6, the compiler will internally perform
*(0x40020C00) |= (3 << 30);
.L3 - Use
const
for safety. Also, usevolatile
keyword when the memory access is expected. Why?
Note that at the end of the day, the compiler will interpret the abstracted code and perform bitwise manipulations internally.
Nayak, K. (2022). Microcontroller Embedded C Programming: Absolute Beginners [Video file]. Retrieved from https://www.udemy.com/course/microcontroller-embedded-c-programming/