Skip to main content

Welcome to a Grasp lesson

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

Lesson 5

Pointer Declaration and Initialization

Recommended resources

Hello! Welcome to your fifth lesson.

In our last session, we explored the dereference operator (*), learning how to use it to "follow" a pointer to its target address to read or modify the data stored there. This powerful indirection is central to C programming.

Today, we'll formalize the process of creating and preparing these pointers for use. This lesson focuses on the precise syntax and best practices for creating pointer variables for different kinds of data. Our learning outcome is to declare and initialize pointers correctly for different data types. Getting this right is the first step in writing safe and predictable pointer-based code, a non-negotiable requirement in a field like automotive software development.

The Anatomy of a Pointer Declaration

A pointer declaration tells the compiler three essential things:

  1. That you are creating a pointer variable.
  2. The name of that variable.
  3. The type of data it is intended to point to.

The general syntax is:

data_type *pointer_name;

Let's break this down:

  • data_type: This is the base type of the pointer. It specifies the type of the data that the pointer will hold the address of.
  • *: The asterisk is the special character that tells the compiler pointer_name is a pointer, not a regular variable of data_type.
  • pointer_name: This is the identifier you will use to refer to your pointer variable.

Here are some examples with common data types:

int an_integer = 42;
double a_double = 3.14;
char a_character = 'A';

int *p_int = &an_integer;
double *p_double = &a_double;
char *p_char = &a_character;

This image provides a clear visual summary of the declaration and initialization process.

Declaration and Initialization of Pointers
This diagram shows the syntax for declaring a pointer, using the address-of operator `&` to get a variable's memory location, and how the pointer variable `ptr` stores the address of the integer variable `x`.

A Note on Style: int* p vs. int *p

You will encounter both int* p; and int *p; in C code. Both are syntactically correct and compile to the same machine code. However, the int *p; style is generally preferred because it aligns more closely with how the C compiler interprets declarations. Consider this line:

int *p, q;

Here, p is declared as a pointer to an int, but q is declared as a regular int. The * binds to the variable name (p), not the type (int). This can be a source of confusion, so it's often clearer to declare one pointer per line.


The Critical Role of the Base Type

You might wonder why we need to specify a pointer's type. After all, isn't an address just an address, regardless of what's stored there? The base type is crucial for two main reasons:

  1. Dereferencing: When you write *p_int = 100;, the compiler uses the pointer's base type (int) to know how many bytes to write to memory (e.g., 4 bytes on a typical 32-bit system). If it were a char*, it would only write 1 byte. Without the type, the compiler wouldn't know the size of the data at the target address.

  2. Pointer Arithmetic: As we will see in the next module, when you perform arithmetic on a pointer (e.g., p_int + 1), the compiler doesn't just add 1 to the raw memory address. It adds 1 * sizeof(int). The base type provides the scale for all pointer arithmetic operations.

The following reading provides a great explanation of pointer declaration and the importance of its type.

A TUTORIAL ON POINTERS AND ARRAYS IN C by Ted ...

To solidify your understanding of pointer declaration and the significance of the base type, please read the following sections from 'A TUTORIAL ON POINTERS AND ARRAYS IN C'.

First, read the section that starts with 'In C when we define a pointer variable...' and ends just before the discussion of the 'NULL' macro. This covers the basic declaration syntax. Then, jump to the beginning of 'CHAPTER 2: Pointer types and Arrays' and read the first two paragraphs, which explain exactly why the base type is so important for dereferencing and arithmetic.


Safe Initialization: Avoiding Disaster

An uninitialized pointer is a ticking time bomb. It holds a random, garbage address. If you try to dereference it, your program will likely crash or, worse, corrupt some unknown part of memory. In safety-critical systems, this is an unacceptable risk. All pointers must be initialized to a known state before use.

There are three safe ways to initialize a pointer:

  1. Initialize with the address of a variable:
    This is the most common use case. You make the pointer point to a valid piece of data as soon as it's created.

    uint32_t can_id = 0x7DF;          // A standard diagnostics CAN ID
    uint32_t *id_ptr = &can_id;       // id_ptr is initialized to the address of can_id
    
  2. Initialize with NULL:
    If you don't have a valid address for the pointer at the time of declaration, initialize it to NULL. NULL is a special macro that represents a "points to nothing" state. This makes it easy to check if a pointer is valid before using it.

    struct SomeIpHeader *header_ptr = NULL;
    
    // ... later in the code ...
    if (header_ptr != NULL) {
        // Safe to dereference
        process_header(header_ptr);
    }
    
  3. Initialize with another pointer:
    You can initialize a pointer with the value of another pointer of the same base type. Both pointers will then point to the same memory location.

    uint8_t *pdu_buffer_ptr = &pdu[0];     // Pointer to the start of a PDU buffer
    uint8_t *payload_ptr = pdu_buffer_ptr; // Both now point to the same buffer
    

The following reading walks through these initialization techniques with clear examples.

Pointer Basics in C - C Programming Tutorial

The article 'Pointer Basics in C' provides a practical overview of how to assign addresses and NULL to pointers.

Please read the sections 'Declaring Pointer Variables' and 'Assigning Address to Pointer Variable'. Focus on the syntax for declaration, the use of the & operator for assignment, and the concept of initializing pointers to NULL.

Test your understanding!

Your task is to write a small code snippet. Imagine you are defining part of an AUTOSAR COM module. You have the following variables:

// A variable holding the Data Length Code (DLC) of a CAN frame
uint8_t dlc = 8;

// A variable for a specific signal value, e.g., vehicle speed
float vehicle_speed = 95.5f;

// A pointer to a large data buffer that is not yet allocated
uint8_t *tx_buffer_ptr; 

Write the C code to declare and initialize three new pointers:

  1. A pointer named dlc_p that points to the dlc variable.
  2. A pointer named speed_p that points to the vehicle_speed variable.
  3. A pointer named rx_buffer_p which, like tx_buffer_ptr, is not yet pointing to a valid buffer. Initialize it to a safe default state.
Show answer

Here is the correct implementation:

// 1. Declare and initialize a pointer to the dlc variable
uint8_t *dlc_p = &dlc;

// 2. Declare and initialize a pointer to the vehicle_speed variable
float *speed_p = &vehicle_speed;

// 3. Declare a pointer for a receive buffer and initialize it to a safe state
uint8_t *rx_buffer_p = NULL;

Each pointer is declared with the correct base type that matches the variable it points to. rx_buffer_p is safely initialized to NULL because it does not yet have a valid memory address to hold.


Conclusion

In this lesson, we established the fundamental rules for creating and initializing pointers in C. Correctly declaring a pointer with its specific base type is not just a matter of syntax; it's what enables the compiler to handle memory operations safely and correctly.

Key Takeaways:

  • The declaration syntax is data_type *pointer_name;.
  • The base type is critical for the compiler to know the size of the data being pointed to, which affects both dereferencing and pointer arithmetic.
  • An uninitialized pointer is a major source of bugs. Always initialize a pointer to a valid address (&variable), to NULL, or to the value of another valid pointer.
  • Adhering to these rules is a cornerstone of the defensive programming required for robust and safe systems, aligning with principles you know from MISRA-C and ISO 26262.

In our next lesson, we will investigate an important property of pointer variables themselves: their size. We'll explore pointer size and its relationship to system architecture (32-bit vs. 64-bit). This will help you understand how pointers behave across the different microcontroller and processor architectures you encounter in your work.