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:
xxxxxxxxxx71struct tag_name2{3 member_element1;4 member_element2;5 member_element3;6 member_element4;7}; /* don't forget this semi-colon! */
structis a reserved keyword in C.
Structure definition example:
xxxxxxxxxx71struct CarModel2{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:
xxxxxxxxxx11struct 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:
xxxxxxxxxx81/* 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:
xxxxxxxxxx21/* 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
xxxxxxxxxx33123
4struct carModel5{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}xxxxxxxxxx111Car BMW information:2carNumber = 20213carPrice = 150004carMaxSpeed = 2205carWeight = 1330.0000006
7Car Ford information:8carNumber = 40319carPrice = 3500010carMaxSpeed = 16011carWeight = 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:
xxxxxxxxxx20123
4struct carModel5{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}xxxxxxxxxx11izeof(struct carModel) = sizeof(carBMW) = 16
%luin 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:
xxxxxxxxxx91 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.
xxxxxxxxxx34123
4struct DataSet5{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}xxxxxxxxxx1310x7ffd2ecbcb6c 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 EE70x7ffd2ecbcb72 FF80x7ffd2ecbcb73 FF90x7ffd2ecbcb74 22 <- start of data3100x7ffd2ecbcb75 0 (padding)110x7ffd2ecbcb76 CD <- start of data4 (can only be assigned to a half-word-aligned address)120x7ffd2ecbcb77 AB13Total memory consumed by this struct variable = 12Due 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.
xxxxxxxxxx71struct data2{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.
xxxxxxxxxx101struct CarModel2{3 uint32_t carNumber;4 uint32_t carPrice;5 uint16_t carMaxSpeed;6 float carWeight;7}; 8
9/* in main() */10struct CarModel carBMW, carFord;xxxxxxxxxx101typedef 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
_tis used for typedef defined data types,_eis used for enum defined data types.
A structure type cannot contain itself as a member.
xxxxxxxxxx81struct CarModel2{3 uint32_t carNumber;4 uint32_t carPrice;5 uint16_t carMaxSpeed;6 float carWeight;7 //struct CarModel carBMW; // not allowed8}; However, structure types can contain pointers to their own types. Such self-referential structures are used in implementing linked lists and binary trees, etc.
xxxxxxxxxx81struct CarModel2{3 uint32_t carNumber;4 uint32_t carPrice;5 uint16_t carMaxSpeed;6 float carWeight;7 struct CarModel *pcarBMW; // allowed8};
Nest structure means "structure inside a structure".
xxxxxxxxxx121struct Data2{3 char data1;4 int data2;5 char data3;6 short data4;7 struct8 {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}xxxxxxxxxx131struct CarModel2{3 uint32_t carNumber;4 uint32_t carPrice;5 uint16_t carMaxSpeed;6 float carWeight;7 struct8 {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:
xxxxxxxxxx11CarBMW.carPrice = 1000;Given the structure variable's address (or pointer), use the ->(arrow operator) to access the member variables:
xxxxxxxxxx21CarModel *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:
xxxxxxxxxx45123
4struct Packet5{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}xxxxxxxxxx101Enter 32-bit packet information (in hex): 0xFFFFFFFF2crc : 0x33status : 0x14payload : 0xfff5bat : 0x76sensor : 0x77longAddr : 0xff8shortAddr : 0x39addrMode : 0x110Size 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.
xxxxxxxxxx45123
4struct Packet5{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}xxxxxxxxxx101Enter 32-bit packet information (in hex): 0xFFFFFFFF2crc : 0x33status : 0x14payload : 0xfff5bat : 0x76sensor : 0x77longAddr : 0xff8shortAddr : 0x39addrMode : 0x110Size 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:
xxxxxxxxxx71Struct CarDetails2{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:
xxxxxxxxxx91Struct CarDetails2{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:

xxxxxxxxxx211/* main.h */2
3typedef struct4{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;xxxxxxxxxx61/* 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
constfor safety. Also, usevolatilekeyword 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/