Skip to main content

Command Palette

Search for a command to run...

Const Keyword

Updated
5 min readView as Markdown

Have you ever wondered how to write safer, more efficient code in C, especially for embedded systems?

I’ve found that the const keyword is a powerful tool that can significantly enhance my programming practices. While it may seem simple, understanding and utilizing const effectively can make a world of difference in the clarity and reliability of my code.

My Encounter

#include <stdint.h>

// Define a constant for the maximum number of sensor readings
const uint8_t MAX_SENSOR_READINGS = 100;

// Function to process sensor data
void processSensorData(const int16_t *sensorData, uint8_t numReadings) {
    // Ensure numReadings does not exceed MAX_SENSOR_READINGS
    if (numReadings > MAX_SENSOR_READINGS) {
        numReadings = MAX_SENSOR_READINGS;
    }

    for (uint8_t i = 0; i < numReadings; i++) {
        // Process each sensor reading
        // The sensorData pointer is read-only
        // This protects the original data from being modified
        int16_t reading = sensorData[i];
        // Implement logic to handle the reading
    }
}

int main() {
    // Example sensor data
    int16_t sensorData[MAX_SENSOR_READINGS] = { /* sensor values */ };

    // Call function to process sensor data
    processSensorData(sensorData, MAX_SENSOR_READINGS);

    return 0;
}

In this example, I define a constant MAX_SENSOR_READINGS to ensure that I don’t exceed the buffer size when processing sensor data. The use of const not only improves code readability but also prevents accidental modification of the sensorData array within the processSensorData function, enhancing the safety and reliability of my embedded code.

Readability

When I declare a variable as const, it clearly communicates to anyone reading my code that this variable is not meant to be modified. This helps in understanding the flow of the program and makes maintenance easier

Protection Against Accidental Changes: 🗡️🛡️

By using const, I protect my data from unintended modifications. In the processSensorData function, declaring sensorData as a pointer to const int16_t ensures that the original sensor readings are preserved, preventing bugs that might arise from accidental writes to the data.

Enhanced Compiler Optimizations: 💪🏼

Compilers can leverage const to optimize code better. Knowing that certain variables won’t change allows the compiler to make assumptions that can lead to more efficient machine code. This is especially important in embedded systems, where memory and processing power are limited.

Facilitating Multithreading: 🧵🧵🧵

In systems that require multithreading, using const can help prevent data races. If multiple threads are reading from a const variable, I can ensure that one thread won’t modify it while another is reading, thus maintaining data integrity.

Clearer Intent: 🕵🏻

The use of const allows me to express my intent more clearly. When I declare constants, I signal to others (and to my future self) that certain values are meant to remain unchanged throughout the program’s execution, which is especially useful in collaborative projects.

Different Ways to Use const

  1. const int a; and int const a;

    • Both declare a as a constant integer. This means the value of a cannot be modified after I initialize it.
  2. const int *a;

    • This declaration means a is a pointer to a constant integer. I can't change the integer value through this pointer, but I can point a to different integers.
  3. int * const a;

    • In this case, a is a constant pointer to an integer. The integer it points to can be modified, but I cannot change where a points.
  4. int const * a const;

    • This one declares a as a constant pointer to a constant integer. Neither the integer value nor the pointer itself can be modified.

More Examples I Love Examples

  1. Improved Code Readability:

    • Using const helps me convey clear intent to anyone reading my code. For example, when I pass parameters to functions, marking them as const indicates that these values shouldn’t change, making my code easier to understand and maintain.
    void processSensorData(const int *data) {
        // Function logic here
        // data cannot be modified
    }
  1. Enhanced Optimization:

    • I’ve noticed that compilers can generate tighter, more efficient code when they know certain values won’t change. This is crucial in embedded systems, where performance and memory usage often have strict limits.
    const int MAX_SENSOR_READINGS = 100;
    for (int i = 0; i < MAX_SENSOR_READINGS; i++) {
        // Process sensor data
    }
  1. Increased Safety and Fewer Bugs:

    • By using const, I protect my code from unintentional modifications. This is especially important in embedded programming, where bugs can lead to system failures or safety issues.
    cCopy codevoid configureDevice(const DeviceConfig *config) {
        // config cannot be modified, ensuring safe access
    }

Even More Examples But Practical

  1. Defining Constant Configuration Values:

     const int LED_PIN = 13;  // Constant for LED pin
     void setup() {
         pinMode(LED_PIN, OUTPUT);
     }
    

    This signifies LED_PIN should not be changed. It prevents any accidental change, the pin stays 13. If you need to change pin number you only need to update the const declaration. 👍🏻

  2. Read-Only Sensor Data:

     void readSensorData(const SensorData *sensor) {
     // sensor data is read-only within this function
     // This is valid: 
     //changing the pointer to point to a different SensorData object
     const SensorData *newSensor = getAnotherSensor();
     // This is NOT valid
     // sensor->value = 10;  // Compiler error
     }
    

    Here the pointer can be changed but the sensor data cannot be, (read only). Integrity +1. 👍🏻

  3. Constant Buffers:

     const uint8_t BUFFER_SIZE = 64;
     uint8_t buffer[BUFFER_SIZE];  // Fixed-size buffer
    

    Since BUFFER_SIZE is declared as const, it can be used in the size declaration of the array. This ensures that the buffer size is determined at compile time, which is important for memory allocation in embedded systems.

    Compiler Optimization +1 👍🏻

Immutable Settings:

  1.    typedef struct {
           const char *name;
           const int version;
       } DeviceInfo;
    
       DeviceInfo device = {"Sensor", 1};  // Device info is read-only
    

The const keyword ensures that the version member of the DeviceInfo struct remains read-only, preventing any modifications after initialization.

Clarity, integrity, optimization 😎

Take Away 🦟🤺

Code that uses const liberally is inherently protected by the compiler against inadvertent coding constructs that result in parameters being changed that should not be.

It is more than just a coding preference—it’s a practice that promotes clarity, safety, and efficiency. By incorporating const into my code, I can create robust applications that are easier to maintain and less prone to errors, all while optimizing performance. Embracing this practice not only benefits my coding but also enhances collaboration with others. So, the next time I write C code, I’ll make sure to consider how const can improve my projects.