На информационном ресурсе применяются рекомендательные технологии (информационные технологии предоставления информации на основе сбора, систематизации и анализа сведений, относящихся к предпочтениям пользователей сети "Интернет", находящихся на территории Российской Федерации)

Feedbox

12 подписчиков

Unionize Your Variables – An Introduction to Advanced Data Types in C

Author: Sven Gregori / Source: Hackaday

Programming C without variables is like, well, programming C without variables. They are so essential to the language that it doesn’t even require an analogy here. We can declare and use them as wildly as we please, but it often makes sense to have a little bit more structure, and combine data that belongs together in a common collection.

Arrays are a good start to bundle data of the same type, especially when there is no specific meaning of the array’s index other than the value’s position, but as soon as you want a more meaningful association of each value, arrays will become limiting. And they’re useless if you want to combine different data types together. Luckily, C provides us with proper alternatives out of the box.

This write-up will introduce structures and unions in C, how to declare and use them, and how unions can be (ab)used as an alternative approach for pointer and bitwise operations.

Structs

Before we dive into unions, though, we will start this off with a more common joint variable type — the struct. A struct is a collection of an arbitrary amount of variables of any data type, including other structs, wrapped together as a data type of its own. Let’s say we want to store three 16-bit integers representing the values of a temperature, humidity, and light sensor.

Yes, we could use an array, but then we always have to remember which index represents what value, while with a struct, we can give each value its own identifier. To ensure we end up with an unsigned 16-bit integer variable regardless of the underlying system, we’ll be using the C standard library’s type definitions from stdint.h.

1234567 #include <stdint.h>struct sensor_data {uint16_t temperature;uint16_t humidity;uint16_t brightness;};

We now have a new data type that contains three integers arranged next to each other in the memory. Let’s declare a variable of this new type and assign values to each of the struct‘s field.

12345 struct sensor_data data;data.temperature = 123;data.humidity    = 456;data.brightness  = 789;

Alternatively, the struct can be initialized directly while declaring it. C offers two different ways to do so: pretending it was an array or using designated initializers. Treating it like an array assigns each value to the sub-variable in the same order as the struct was defined. Designated initializers can be arbitrarily assigned by name. Once initialized, we can access each individual field the same way we just assigned values to it.

123456789101112131415 struct sensor_data array_style = {123, /* temperature */456, /* humidity    */789  /* brightness  */};struct sensor_data designated_initializers = {.humidity    = 456,.temperature = 123,.brightness  = 789};printf("Temperature: 0x%d\n", array_style.temperature);printf("Humidity:    0x%d\n", array_style.humidity);printf("Brightness:  0x%d\n", array_style.brightness);

Notice how the fields in the designated initializers are not in their original order, and we could even omit individual fields and leave them simply uninitialized. This allows us to modify the struct itself later on, without worrying much about adjusting every place it was used before — unless of course we rename or remove a field.

Bitfields

The bitfield is a special-case struct that lets us split up a portion of an integer into its own variable of arbitrary bit length. To stick with the sensor data example, let’s assume each sensor value is read by an analog-to-digital converter (ADC) with 10-bit resolution.

Storing the results in 16-bit integers will therefore waste 6 bits for each value, which is more than one third. Using bitfields will let us use a single 32-bit integer and split it up in three 10-bit variables instead, leaving only 2 bits unused altogether.

12345 struct sensor_data_bitfield {uint32_t temperature:10;uint32_t humidity:10;uint32_t brightness:10;};

We could also add a 2-bit wide fourth field to use the remaining space at no extra cost. And this is pretty much all there is to know about bitfields. Other than adding the bit length, bitfields are still just structs, and are therefore handled as if they were just any other regular struct. Bitfields can be somewhat architecture and compiler dependent, so some caution is required.

Unions

Which brings us to today’s often overlooked topic, the union. From the outside, they look and behave just like a struct, and are in fact declared, initialized and accessed the exact same way. So to turn our struct sensor_data into a union, we simply have to change the keyword and we are done.

12345 union sensor_data {uint16_t temperature;uint16_t humidity;uint16_t brightness;};

However, unlike a struct, the fields inside a union are not arranged in sequential order in the memory, but are all located at the same address. So if a struct sensor_data variable starts at memory address 0x1000, the temperature field will be located at 0x1000, the humidity field at 0x1010, and the brightness field at address 0x1020. With a union, all three fields will be located at address 0x1000.

What this means in practice is easily shown once we assign values to all the fields like we did in the struct example earlier.

1234567 union sensor_data data;data.temperature = 123;data.humidity    = 456;data.brightness  = 789;printf("Temperature: 0x%d\n", data.temperature);

Unlike the struct example,…

Click here to read more

The post Unionize Your Variables – An Introduction to Advanced Data Types in C appeared first on FeedBox.

Ссылка на первоисточник
наверх