What are Pointer Notation for Arrays- C Programming Course in Telugu

Need Help or More Information?

Questions about this free C programming course, embedded C training or career guidance? Get in touch.

Free C Programming Course · Pointers · Lecture 18

Pointer Notation for Arrays

Read and write array elements with the dereference operator, see why arr[i] and *(arr + i) are the same thing, and test the old claim that pointer notation is faster. A practical lesson in our free C tutorial for embedded systems.

Topics in This Lecture

pointer notation for arrays *(arr + i) arr[i] equivalence dereferencing arrays C programming for embedded systems free C tutorial

So far in this free C programming course you have learned where array elements live and how to compute their addresses. Now we add the final ingredient: the dereference operator. Combining the two gives pointer notation for arrays, the form *(arr + i), which the compiler treats as exactly equivalent to arr[i]. Understanding this equivalence is a milestone in embedded C programming, because driver code, firmware libraries and the Linux kernel freely mix the two styles.

We will also test, with real compiler output, a statement found in many older books: that pointer notation is faster than subscripts. The answer may surprise you.

What You Will Learn

  • How *arr and *(arr + i) read element values
  • Why arr[i] is defined as *(arr + i)
  • The curious but legal form i[arr] and why you should never write it
  • How to write to array elements using pointer notation
  • How to walk an array with a moving pointer and an end pointer
  • What the compiler really generates for both styles
  • The precedence trap in *arr + 1

Prerequisites

You need the two previous lectures: array memory layout and address arithmetic with arr + i. You should also remember that the unary * operator follows a pointer to the value it points at.

Dereferencing an Array Name

An address on its own tells you where something lives. Applying * to that address gives you the object stored there. Because an array name converts to the address of its first element, the expression *arr is the value of the first element, the same as arr[0].

For the element at position i, first compute the address with arr + i, then dereference it. The parentheses matter, and we will see why in a moment.

From array name to element value

arr→ address of first element+ i→ address of element i*→ value of element i

The following table maps the two notations side by side for a five-element array called temp.

Pointer notationSubscript notationMeaning
temp&temp[0]Address of element 0
temp + i&temp[i]Address of element i
*temptemp[0]Value of element 0
*(temp + i)temp[i]Value of element i

Why arr[i] Is the Same as *(arr + i)

This is not a coincidence or an optimisation. The C language defines the subscript operator in exactly these terms: the expression E1[E2] is by definition *((E1) + (E2)). When you write arr[3], the compiler rewrites it as *(arr + 3) in its own mind and carries on. Subscript notation is simply friendlier syntax laid on top of pointer arithmetic.

A direct consequence is that anything which works for one form works for the other, including assignment, compound assignment, increment and passing the element’s address to a function.

Example 1: Four Ways to Read the Same Element

This program prints each element using the four spellings side by side, then modifies two elements through pointer notation.

#include <stdio.h>

int main(void)
{
    int temp[5] = {18, 21, 24, 27, 30};

    for (int i = 0; i < 5; i++) {
        printf("i=%d  temp[i]=%d  *(temp+i)=%d  *(i+temp)=%d  i[temp]=%d\n",
               i, temp[i], *(temp + i), *(i + temp), i[temp]);
    }

    *temp = 15;
    *(temp + 4) += 5;
    printf("after writes: first=%d last=%d\n", temp[0], temp[4]);
    return 0;
}

Build and run

gcc -Wall -Wextra -std=c11 -o l18_equiv l18_equiv.c
./l18_equiv

Expected output

i=0  temp[i]=18  *(temp+i)=18  *(i+temp)=18  i[temp]=18
i=1  temp[i]=21  *(temp+i)=21  *(i+temp)=21  i[temp]=21
i=2  temp[i]=24  *(temp+i)=24  *(i+temp)=24  i[temp]=24
i=3  temp[i]=27  *(temp+i)=27  *(i+temp)=27  i[temp]=27
i=4  temp[i]=30  *(temp+i)=30  *(i+temp)=30  i[temp]=30
after writes: first=15 last=35

Reading the result

  • All four columns match on every line, confirming the equivalence.
  • *temp = 15 replaces the first element, so temp[0] becomes 15.
  • *(temp + 4) += 5 adds 5 to the last element, turning 30 into 35. Pointer notation works on the left side of assignments just like subscripts do.

The Strange Form i[arr]

Addition is commutative, so arr + i and i + arr are the same address. Since arr[i] means *(arr + i), it follows that *(i + arr) is also valid, and that can be spelled i[arr]. The fifth column in our output proves it compiles and works.

Know it, never use it. i[arr] is a curiosity that shows you how the language is defined. It is a favourite of interview questions and obfuscation contests, but in real firmware it only confuses readers and code reviewers. Always write arr[i].

Precedence Trap: *arr + 1 vs *(arr + 1)

The unary * binds tighter than binary +. So *temp + 1 means “take the first element, then add one to the value”, while *(temp + 1) means “move to the second element, then read it”. With our original array, the first gives 19 and the second gives 21. Forgetting the parentheses is one of the most common beginner errors in pointer notation for arrays.

ExpressionStepsResult for {18, 21, 24, 27, 30}
*temp + 1Read element 0, then add 119
*(temp + 1)Move to element 1, then read21
*(temp + 4)Move to element 4, then read30

Walking an Array With a Moving Pointer

Once you accept that elements are reached through addresses, a second looping style becomes natural: keep a pointer, read through it, and advance it. Instead of an index that is converted to an address on every iteration, you carry the address itself.

Index loop vs pointer-walk loop

Index stylei = 0→read a[i]→i++→stop when i == n
Pointer stylep = a→read *p→p++→stop when p == end

Example 2: Index Style vs Pointer Style

Two functions add up the same six readings. One uses subscripts, the other uses a walking pointer with an end marker.

#include <stdio.h>

static int sum_index(const int *a, int n)
{
    int total = 0;
    for (int i = 0; i < n; i++)
        total += a[i];
    return total;
}

static int sum_walk(const int *a, int n)
{
    int total = 0;
    for (const int *p = a, *end = a + n; p != end; p++)
        total += *p;
    return total;
}

int main(void)
{
    int load[6] = {4, 8, 15, 16, 23, 42};
    printf("index style: %d\n", sum_index(load, 6));
    printf("walk  style: %d\n", sum_walk(load, 6));
    return 0;
}
gcc -Wall -Wextra -std=c11 -o l18_walk l18_walk.c
./l18_walk
index style: 108
walk  style: 108

Both give 108. Parameters are declared const int * because the functions only read the data, which documents intent and lets the compiler catch accidental writes.

Is Pointer Notation Faster Than Subscripts?

Many older textbooks state that accessing elements through pointers is faster because the compiler converts subscripts to pointer form anyway. The reasoning sounds neat, but the conclusion does not hold on modern compilers. Both versions describe the same address calculation, and an optimising compiler is free to generate whatever instructions it likes for either.

We checked this with GCC 13 on x86-64 at -O2, preventing inlining so each function stays separate. Looking at the assembly, the work done in the loop body of both functions is the same four steps: load and add the element, advance the pointer by 4 bytes, compare with the end, and branch back.

addl   (%rdi), %eax
addq   $4, %rdi
cmpq   %rdx, %rdi
jne    .loop

To repeat the experiment yourself, mark both functions with GCC’s __attribute__((noinline)), then run:

gcc -O2 -S -o sum.s l18_walk.c
grep -n "addl" sum.s

The only differences between the two functions were the handling of the empty-array case before the loop. The lesson: choose the style that reads best, and measure on your actual target if performance matters. On very small microcontrollers with weak compilers the story can differ, but never assume; look at the generated code.

Use Cases in Embedded Firmware

  • Checksum and CRC loops: a byte pointer walks a buffer until it reaches the end pointer.
  • Driver code: kernel and HAL code often steps through descriptor arrays using pointers.
  • Parsers: a cursor pointer advances as fields are consumed from a received frame.
  • Table lookups: *(table + index) and table[index] are interchangeable; use the latter for clarity.

Common Mistakes and Troubleshooting

MistakeSymptomFix
*arr + i instead of *(arr + i)Always reads element 0 and adds iAdd parentheses around the address expression
Loop with p <= endReads one element past the arrayUse p != end or p < end
Writing through a const pointerCompile errorRemove const only if writing is intended
Using i[arr] in real codeReviewers confusedWrite arr[i]
Modifying the array while a pointer walks itUnexpected resultsKeep reading and writing roles clear

Best Practices, Performance and Security

Best practices

  • Prefer arr[i] when you think in positions, and a walking pointer when you think in sweeps.
  • Always parenthesise *(arr + i).
  • Mark read-only data with const.
  • Pass the element count along with the pointer, because the callee cannot know it.

Performance: do not rewrite working subscript code into pointer style hoping for speed. Measure, or read the assembly.

Security: a mistake in the end condition silently reads or overwrites neighbouring memory. Treat any length supplied by a remote peer as untrusted and check it against the buffer size before looping.

Summary and Key Takeaways

  • *(arr + i) is the value of element i, and arr[i] is defined as exactly that.
  • *arr is the first element; *arr + 1 and *(arr + 1) are very different.
  • i[arr] works but should never appear in real code.
  • Pointer notation can be used for reading and writing.
  • Index loops and pointer-walk loops can compile to the same machine instructions.

Conclusion

You now know that subscripting is pointer arithmetic plus dereferencing in a friendlier outfit. That knowledge helps you read any C source, from small firmware to operating system kernels, without being thrown by unfamiliar notation. The final lecture of this group completes the picture by comparing an array name with a pointer variable: they look alike but they are not the same, and the differences matter. Re-run the programs here, change the element values, and predict the output of each column before you run.

Frequently Asked Questions

Is arr[i] really identical to *(arr + i)?

Yes. The C standard defines the subscript operator as the dereference of the sum of the pointer and the index.

Why does i[arr] compile?

Because addition is commutative, i + arr equals arr + i, so *(i + arr) is valid and can be written i[arr]. It is legal but poor style.

Is pointer notation faster than array subscripts in C?

Not on modern optimising compilers. Both forms describe the same address calculation and typically produce the same loop code. Check the assembly or benchmark on your target.

What is the difference between *arr + 1 and *(arr + 1)?

The first reads element 0 and adds 1 to the value. The second moves to element 1 and reads it, because the parentheses force the address calculation first.

Can I assign through pointer notation?

Yes. Statements such as *(arr + 2) = 9 and *arr += 3 modify array elements exactly like subscript assignments.

Do I need this for embedded C programming?

Yes. Firmware, HAL libraries and kernel drivers mix both styles, so reading and writing pointer notation fluently is essential and is a core topic of this free embedded C course.

Finish the Array and Pointer Story

The next lecture compares an array name with a pointer variable, and includes a quiz and interview questions. Keep going with our free C programming course.

Need Help or More Information?

Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *