Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 68

MISRA-C Pointer Guidelines

Recommended resources

Hello! Welcome to the ninth lesson in your C pointers course.

In our last session, we took a reactive approach, diving deep into debugging pointer-related faults in an embedded context using Trace32. We saw firsthand how issues like memory corruption and null pointer dereferences manifest at the hardware level. Today, we shift our focus from reaction to prevention. Your work with ISO 26262 and ASPICE already emphasizes the importance of process and proactive safety measures, and that's precisely where this lesson fits.

Our learning outcome is to identify key MISRA-C guidelines governing pointer casting and arithmetic. Instead of just learning the rules, we will focus on understanding the rationale behind them—why they exist and how they prevent the very bugs we just learned to hunt down. This is a cornerstone of writing safe, reliable, and maintainable C code in the automotive industry.

1. The Philosophy of MISRA-C: Proactive Safety

Before we dive into specific rules, it's helpful to remember the MISRA C philosophy. As you know from your experience, it's not about making C a different language; it's about defining a "safer" subset of it. The goal is to eliminate language behaviors that are undefined, unspecified, or simply prone to developer error, which are unacceptable in high-integrity systems.

The following resource provides a great overview of MISRA's history and structure.

MISRA C Standard

The article 'MISRA C Standard' provides context on the standard's purpose. We'll focus on the classification of its guidelines.

Please read the first half of this article, from the start down to the end of the section describing the guideline categories ('Mandatory', 'Required', 'Advisory'). This will provide a solid refresher on the structure and intent of the MISRA standard.

The key takeaway is that MISRA provides a framework for discipline. The two main pointer-related pitfalls we'll discuss—unstructured arithmetic and unsafe casting—are prime examples of C features that offer power at the cost of safety. MISRA guidelines steer us towards more robust patterns.

The overarching principle is captured in Directive D.4.1: Run-time failures shall be minimized. The article you just read briefly mentions this and specifically calls out "Pointer Arithmetic" as an area prone to run-time errors. Let's start there.


2. Pointer Arithmetic: Favor Clarity and Safety

One of the most common sources of pointer errors is complex arithmetic. Calculating an offset, scaling it by the size of the type, and adding it to a base address is powerful but also easy to get wrong, leading to buffer overflows or out-of-bounds access.

MISRA C:2023 addresses this directly with a very clear rule.

MISRA C:2023 Rule 18.4

The MathWorks page on 'MISRA C:2023 Rule 18.4' perfectly encapsulates the modern approach to pointer arithmetic. It provides the rule, the rationale, and excellent code examples.

Please read the 'Rule Definition', 'Rationale', and review the code examples. Focus on the distinction between what is considered compliant and non-compliant.

Key Guideline: Rule 18.4

The core of this rule is simple: The +, -, +=, and -= operators should not be applied to a pointer.

  • Non-compliant: *(ptr + 5) = 0U;
  • Compliant: ptr[5] = 0U;

Rationale:

  1. Clarity and Readability: The array subscript syntax ptr[5] is unambiguous. It clearly states the intent: "access the 5th element relative to ptr". The pointer arithmetic form *(ptr + 5) requires more cognitive effort to parse and is less direct. In a complex expression, it can become very difficult to read.
  2. Error Prevention: While both forms can lead to out-of-bounds access, the array-style syntax is generally considered easier to visually inspect and for static analysis tools to reason about. The expression ptr + 1 can be misinterpreted by new developers, as the actual byte offset depends on sizeof(*ptr). Array notation hides this complexity, reducing the chance of manual miscalculation.
  3. Permitted Exceptions: The rule specifically allows the use of ++ and --. This is a pragmatic concession. Idioms like while (*dst++ = *src++); are deeply ingrained in C programming for efficient data copying and are generally well-understood.

In your work with AUTOSAR COM stacks, where you might be iterating through PDU buffers or signal data, adhering to this rule makes the code more auditable for functional safety reviews.


3. Pointer Casting: Preserving Type Safety

Casting pointers is another powerful feature that is heavily restricted by MISRA. Improper casting can undermine the type system and lead to alignment faults, incorrect data interpretation, and violations of the strict aliasing rule. Your experience with diverse microcontroller architectures in ADAS systems will make the importance of these rules immediately apparent.

The article "MISRA C Standard" that you looked at earlier contains the key rules for casting.

MISRA C Standard

Let's return to the 'MISRA C Standard' article to examine the rules on casting.

Please read the section titled 'Rule R.11.3', which covers two closely related rules about pointer conversions.

Key Guidelines: Rule 11.x

MISRA has a suite of rules under 11.x governing conversions. The two most critical for our discussion are:

1. A cast shall not be performed between a pointer to object type and a pointer to a different object type (Rule 11.3).

  • Example: uint32_t *p32 = (uint32_t *)p8; where p8 is uint8_t *.
  • Why it's dangerous:
    • Alignment: An architecture might require uint32_t to be on a 4-byte boundary. If p8 points to an address that is not a multiple of 4, dereferencing p32 can cause a hardware alignment fault. This is a common issue on many ARM Cortex-M cores used in ECUs.
    • Strict Aliasing: This cast can lead to violations of the C standard's strict aliasing rule, where the compiler assumes that pointers of different types do not point to the same memory. Violating this can lead to unpredictable behavior when optimizations are enabled, as the compiler might reorder reads and writes in unexpected ways.
    • The void* Exception: The primary exception to this rule is casting to and from void*. This is the designated mechanism for generic programming in C, which you've seen in functions like memcpy or in OS-level APIs.

2. A conversion should not be performed between a pointer to object and an integer type (Rule 11.4 in MISRA C:2012).

  • Example: int addr_value = (int) &data;
  • Why it's dangerous:
    • Portability & Data Loss: The core issue is that you cannot assume an integer type (like int or long) is large enough to hold a full memory address. On a 64-bit system, a pointer is 8 bytes, but an int may only be 4 bytes. Casting the pointer to an int would truncate the address, leading to catastrophic failure when it's used later.
    • The Correct Way (When Necessary): When you absolutely must store a pointer value as an integer (a rare and carefully scrutinized operation in safety-critical code), the standard provides uintptr_t and intptr_t in <stdint.h>. These types are guaranteed to be large enough to hold a pointer. However, MISRA still heavily restricts this practice to prevent developers from performing arithmetic on these integer representations and then casting back, bypassing the type system entirely.
Test your understanding!

Review the following code snippet, which processes a raw CAN message payload. Identify at least four distinct MISRA C violations related to pointer arithmetic and casting, and explain the rationale for each violation based on the rules we've discussed.

#include <stdint.h>

void process_payload(uint8_t *payload) {
    uint16_t signal1;
    uint32_t signal2;
    uint32_t temp_addr;

    // Violation 1?
    signal1 = *(uint16_t *)(payload + 2);

    // Violation 2?
    temp_addr = (uint32_t)(payload); 
    
    // Violation 3?
    temp_addr += 4; 

    // Violation 4?
    signal2 = *(uint32_t *)temp_addr;
}
Show answer

Here are four violations in the code:

  1. Violation: (payload + 2)

    • Rule: 18.4 (The + operator should not be applied to a pointer).
    • Rationale: This is explicit pointer arithmetic. The compliant way to express this would be &payload[2].
  2. Violation: *(uint16_t *)...

    • Rule: 11.3 (A cast shall not be performed between a pointer to object type and a pointer to a different object type).
    • Rationale: The code casts a uint8_t* (or the result of an operation on it) to a uint16_t*. This breaks type safety and can cause an alignment fault if payload + 2 is not on a 2-byte boundary. It also violates strict aliasing.
  3. Violation: temp_addr = (uint32_t)(payload);

    • Rule: 11.4 (A conversion should not be performed between a pointer and an integer type).
    • Rationale: The code casts a pointer payload to a uint32_t. On a 64-bit system, this would truncate the address, leading to data loss. Even on a 32-bit system where the sizes match, this is poor practice. uintptr_t should be used if this is unavoidable, but the entire pattern is discouraged.
  4. Violation: signal2 = *(uint32_t *)temp_addr;

    • Rule: 11.4 (A conversion should not be performed between an integer and a pointer type).
    • Rationale: This line completes the anti-pattern from the previous violation by casting an integer temp_addr back into a pointer. This bypasses the type system entirely. Any arithmetic done on temp_addr as an integer is opaque to the compiler and static analysis tools, making it impossible to verify pointer safety.

Conclusion

Today, we've examined how MISRA C codifies safer programming practices by placing specific restrictions on pointer usage. These aren't arbitrary rules; they are defensive measures designed to prevent entire classes of bugs that are common in C and unacceptable in safety-critical automotive software.

Key Takeaways:

  • Prefer Array Notation: Use ptr[index] instead of *(ptr + index). It is clearer, safer, and more amenable to static analysis.
  • Avoid Pointer-to-Object Casts: Do not cast between pointers of different object types (e.g., uint8_t* to uint32_t*), as this can cause alignment faults and aliasing issues. Use void* for intentional generic programming.
  • Do Not Mix Pointers and Integers: Avoid casting pointers to integer types and vice-versa. This breaks portability and obscures pointer operations from the compiler and analysis tools.

By internalizing these rules, you are not just ensuring compliance; you are writing code that is fundamentally more robust, readable, and less likely to contain the subtle, dangerous bugs we explored in the previous lesson.


Preview of the Next Lesson

Knowing the rules is the first step. The next is applying them. In our next lesson, we will analyze C code to detect violations of critical MISRA-C pointer safety rules. You'll take on the role of a code reviewer, armed with your knowledge from today, and learn to spot these non-compliances in larger, more realistic code examples, similar to how static analysis tools like Coverity or Helix QAC operate.