How to Use Pointer Ampersand Notation in C

Jinku Hu Feb 02, 2024
  1. Use &var Notation to Get the Address of a Given Variable
  2. Use the *ptr Notation to Access the Value of a Variable From the Pointer
  3. Use the Ampersand Notation & to Pass the Address of Objects to Functions
  4. Conclusion
How to Use Pointer Ampersand Notation in C

Understanding pointers and their associated operators is fundamental to effective programming in the C language. Among these operators, the ampersand (&) and dereference (*) play crucial roles in memory manipulation and dynamic allocation.

The & operator, known as the address-of operator, allows developers to obtain the memory address of a variable, enabling powerful applications such as passing addresses to functions and dynamic memory allocation. On the other hand, the dereference operator (*) allows access to the value stored at a particular memory address, facilitating indirect variable manipulation.

This article explores the applications of these operators in C, emphasizing their significance in pointer-based programming.

Use &var Notation to Get the Address of a Given Variable

In C, a pointer is a variable that holds the memory address of another variable. To declare a pointer, we can use the type *var notation, where type represents the data type of the variable the pointer will point to.

The pointer can be assigned any address of the same type, and any pointer type can store the void* pointer, which is called a generic pointer.

The ampersand & operator, when placed before a variable, becomes a powerful tool for obtaining the memory address at which that variable is stored. This operator is often referred to as the address-of operator.

Let’s have a look at an example to see how it works:

#include <stdio.h>

int main() {
  int x = 42;
  int *ptr = &x;  // Using & to assign the address of 'x' to 'ptr'

  printf("Address of x is %p\n", &x);
  printf("Value of ptr is %p\n", ptr);

  return 0;
}

Output:

Address of x is 0x7fff848c957c
Value of ptr is 0x7fff848c957c

In this example, the &x expression retrieves the address of the integer variable x. The %p format specifier in the printf function is used to print the address in a pointer format.

Consequently, ptr now holds the address of x, and it can be utilized to indirectly access and manipulate the data stored in x.

Pointers and Function Parameters

One common application of the & notation is passing variable addresses to functions. This practice allows functions to directly modify the content of variables, enabling more efficient memory usage.

#include <stdio.h>

void modifyValue(int *ptr) { *ptr = 99; }

int main() {
  int num = 42;

  printf("Before: %d\n", num);

  modifyValue(&num);

  printf("After: %d\n", num);

  return 0;
}

Output:

Before: 42
After: 99

In this example, the modifyValue function takes a pointer to an integer as its parameter and changes the value at that memory location to 99 using the dereference operator.

In the main function, an integer variable num is initialized with the value 42, and its initial value is printed. The modifyValue function is then called, passing the address of the num variable.

After the function call, the updated value of num is printed, demonstrating how the function effectively alters the value of the variable through pointer manipulation.

Dynamic Memory Allocation

The & notation is fundamental when working with dynamic memory allocation using functions like malloc and calloc. These functions return a pointer to the allocated memory block, making it essential to use the & notation to obtain the memory address for proper manipulation.

#include <stdio.h>
#include <stdlib.h>

int main() {
  int *dynamicArray;

  dynamicArray = (int *)malloc(5 * sizeof(int));

  if (dynamicArray == NULL) {
    printf("Memory allocation failed.\n");
    return 1;
  }

  // Using the obtained address for dynamic memory manipulation
  // ...

  free(dynamicArray);

  return 0;
}

As we can see inside the main function, a pointer dynamicArray is declared to hold the address of the dynamically allocated memory. The malloc function is used to allocate memory for an array of 5 integers, and the size is specified using sizeof(int).

The program checks if the allocation is successful, printing an error message if it fails and exiting with a status of 1. After successful allocation, the obtained memory address can be used for dynamic memory manipulation, as indicated by a comment.

Finally, the allocated memory is freed using the free function, and the program returns 0, signifying successful execution.

Use the *ptr Notation to Access the Value of a Variable From the Pointer

Another fundamental aspect of working with pointers is the dereference operator, denoted by the * symbol. This operator plays a crucial role in accessing and manipulating the values stored at memory addresses pointed to by variables.

When a pointer is declared with type *ptr notation, the *ptr expression retrieves the actual value stored at the memory location to which ptr points. Note that the * operator can’t be used with null or invalid pointers because it results in undefined behavior.

Here’s an example code:

#include <stdio.h>

int main() {
  int x = 42;
  int *ptr = &x;

  printf("Value of x using *ptr: %d\n", *ptr);

  return 0;
}

Output:

Value of x using *ptr: 42

In this example, a variable x is initialized with the value 42. A pointer *ptr is then declared and assigned the memory address of x using the address-of operator &.

The program proceeds to print the value of x indirectly through the pointer ptr using the dereference operator *.

The output of the program will display the Value of x using *ptr: 42, showcasing the ability to access the value of x through the pointer.

Dereferencing for Value Assignment

One common application of the *ptr notation is to assign values through pointers, facilitating indirect manipulation of variables. Consider the following example:

#include <stdio.h>

int main() {
  int x = 42;
  int *ptr = &x;

  *ptr = 99;

  printf("New value of x: %d\n", x);

  return 0;
}

Output:

New value of x: 99

Here, the same variable x is initialized with the value 42. Also, a pointer ptr is declared and assigned the address of x using the address-of operator &.

This time, the program utilizes the dereference operator * to assign a new value of 99 to the variable x indirectly through the pointer *ptr. Consequently, the program prints the updated value of x as New value of x: 99.

Use the Deference Operator for Dynamic Memory Allocation

Dynamic memory allocation frequently involves using the *ptr notation to access and manipulate values in allocated memory. In scenarios where memory is allocated using functions like malloc or calloc, the dereference operator becomes instrumental.

#include <stdio.h>
#include <stdlib.h>

int main() {
  int *dynamicArray;

  dynamicArray = (int *)malloc(5 * sizeof(int));

  if (dynamicArray == NULL) {
    printf("Memory allocation failed.\n");
    return 1;
  }

  *dynamicArray = 42;

  printf("Value in dynamicArray: %d\n", *dynamicArray);

  free(dynamicArray);

  return 0;
}

Output:

Value in dynamicArray: 42

In this example, a pointer variable dynamicArray is declared to store the address of dynamically allocated memory. The malloc function is then utilized to allocate memory for an array of 5 integers, and the size is determined using sizeof(int).

As you can see, the program includes an error-checking mechanism to ensure the success of the memory allocation; if allocation fails, an error message is printed, and the program exits with a return value of 1.

Assuming successful allocation, the dereference operator * is used to assign a value of 42 to the first element of the dynamically allocated array.

Then, the program proceeds to print this value using the printf function. Finally, the allocated memory is released using the free function.

Use the Ampersand Notation & to Pass the Address of Objects to Functions

Another common application of the ampersand notation is in passing the addresses of objects as arguments to functions. By doing so, functions gain direct access to the memory locations of variables, enabling them to modify the contents efficiently.

Passing the address of objects to functions is particularly useful when dealing with large data structures or arrays. Instead of passing the entire data structure, which could be memory-intensive, we can pass the address, reducing the overhead associated with function calls.

Consider the following example:

#include <stdio.h>

void modifyValue(int *ptr) { *ptr = 10133; }

int main() {
  int num = 10233;

  printf("Before modifying: %d\n", num);

  modifyValue(&num);

  printf("After modifying: %d\n", num);

  return 0;
}

Output:

Before modifying: 10233
After modifying: 10133

Here, we have a function named modifyValue designed to alter the value of a variable through a pointer. In the main function, an integer variable named num is initialized with the value 10233.

Subsequently, the modifyValue function is called, passing the address of num as an argument. Inside the function, the dereference operator is employed to change the value at the memory location pointed to by the provided pointer to 10133.

Lastly, the program prints the modified value of num using printf. Despite the modification within the function, the program demonstrates that the original variable in the main function remains unaffected, emphasizing the concept of passing variable addresses to functions in C.

As you can notice, functions that need to modify the values of variables directly benefit from object address passing. This approach eliminates the need for returning values from functions and provides a more direct and memory-efficient means of updating variable content.

Here’s another example code:

#include <stdio.h>

void incrementValue(int *ptr) { (*ptr)++; }

int main() {
  int counter = 5;

  printf("Before increment: %d\n", counter);

  incrementValue(&counter);

  printf("After increment: %d\n", counter);

  return 0;
}

Output:

Before increment: 5
After increment: 6

In the main function, you can see an integer variable named counter is initialized with the value 5. The program then proceeds to print the initial value of counter using printf.

Subsequently, the incrementValue function is invoked, passing the address of counter as an argument. Inside the function, the dereference operator is utilized to increment the value at the memory location pointed to by the provided pointer.

Finally, the program prints the updated value of counter using printf.

This code exemplifies the concept of passing variable addresses to functions in C. It shows how modifications made within the function directly affect the original variable.

Conclusion

Knowing the use of the & and * operators is key to harnessing the full potential of pointers in C. The ability to obtain and manipulate memory addresses opens avenues for efficient function parameter passing, dynamic memory allocation, and indirect variable manipulation.

Whether it’s modifying values through pointers, dynamically allocating memory, or passing object addresses to functions, the careful use of these operators enhances code efficiency and readability. As developers delve into the intricacies of C programming, a solid grasp of the & and * notations proves invaluable for effective memory management and manipulation.

Author: Jinku Hu
Jinku Hu avatar Jinku Hu avatar

Founder of DelftStack.com. Jinku has worked in the robotics and automotive industries for over 8 years. He sharpened his coding skills when he needed to do the automatic testing, data collection from remote servers and report creation from the endurance test. He is from an electrical/electronics engineering background but has expanded his interest to embedded electronics, embedded programming and front-/back-end programming.

LinkedIn Facebook

Related Article - C Pointer