Skip to content
VLSI Mentor

C Part I · Module 7 · Pointers

Pointer Types and Why They Matter

Chapter 7.1 showed that a pointer to char, a pointer to int and a pointer to double were all eight bytes on the same machine, and all of them held the same kind of value — an address. That raises a reasonable question: if the representation is identical and the value means the same thing, why does C bother having separate types at all, rather than one pointer type that can hold any address? The answer is that an address is genuinely not enough information to do anything with. Knowing where an object begins tells you nothing about how far it extends or what its bits are supposed to mean, and both of those are needed the instant you try to use it. The type is where that missing information lives, and this chapter shows the gap it fills using nothing more than sizeof.

Foundation16 min readCPart IFundamentalsPointers

Part I · Module 7 · Chapter 7.3 · Pointer Types and Why They Matter

1. What an Address Does Not Tell You

An address identifies a position in storage — Chapter 2.2's picture, now concrete. Suppose you are handed one, and nothing else.

You know where something starts. You do not know:

  • how many bytes belong to it
  • what those bytes mean — a signed integer, a floating-point value, a character

Both are required before anything can be read. Chapter 2.5 established that int and char and double differ precisely in how many bytes they occupy and how those bits are interpreted, so an address with no type attached is an instruction with the operands missing.

A pointer type is the address plus the answer to those two questions. int * means the address of an object that is an int, and the int part is doing real work.

2. The Evidence, Without Dereferencing Anything

Two pointers, same address, different types:

Azvya Education Pvt. Ltd.VLSI Mentor
how much does each one read?
#include <stdio.h>

int main(void)
{
    int value = 0x11223344;

    int  *pi = &value;
    char *pc = (char *)&value;

    printf("*pi            %d  (0x%08X)\n", *pi, (unsigned)*pi);
    printf("sizeof *pi     %zu bytes read\n", sizeof *pi);
    printf("sizeof *pc     %zu byte read\n", sizeof *pc);

    return 0;
}

Output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
*pi            287454020  (0x11223344)
sizeof *pi     4 bytes read
sizeof *pc     1 byte read

pi and pc hold the same address. Dereferencing pi involves four bytes; dereferencing pc involves one.

Nothing in the address distinguishes those two operations. The number is identical. The only thing that differs is the type of the pointer, and that is the entire point:

The pointer's type is what tells the compiler how many bytes *p covers and how to interpret them.

The four bytes of an int object are shown in a row: byte 0, where the object starts, then bytes 1, 2 and 3. Below them, a band spanning all four bytes is labelled: dereferencing through pointer-to-int reads this much — four bytes, interpreted as one int. Below that, a band the width of a single byte is labelled: dereferencing through pointer-to-char reads this much — one byte, as a char. Both pointers hold the address of byte 0.byte 0value starts herebyte 1byte 2byte 3*pi reads this muchfour bytes, interpreted as one int*pc readsthis muchone byte, as acharboth hold thisaddress12
Figure 1 — why the type is not decoration. The object value occupies four bytes, and both pointers hold the address of the first one. Dereferencing through int * covers all four bytes and interprets them as a single int; dereferencing through char * covers one byte and interprets it as a char. The two bands are drawn to the width each dereference actually reaches. Which byte char * reaches first depends on the implementation's byte order, which Chapter 14.3 covers; nothing in this chapter depends on the answer.

Note what the program did not do: it never printed the byte that *pc would produce. That value depends on the order in which an implementation stores the bytes of an int, which is Chapter 14.3's subject. The argument here needs only the widths, and the widths are enough.

3. The Second Reason, Named Now

There is another job the type does, and it is at least as important. It becomes visible as soon as a pointer is moved rather than merely dereferenced — advance this pointer to the next object has to know how far "next" is, and only the type knows.

Chapter 7.8 owns that completely. It is mentioned here so that you do not finish this chapter believing the read width is the whole story.

4. The Compiler Enforces It

Because the type carries meaning, C does not let you mix pointer types silently:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    int i = 1;
    char *pc = &i;          /* &i has type int * */
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
warning: incompatible pointer types initializing 'char *' with an expression
    of type 'int *' [-Wincompatible-pointer-types]

The same applies to assignment between pointer types and to passing an argument:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    void takes_int_pointer(int *p);
    double d = 1.0;
    takes_int_pointer(&d);
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
warning: incompatible pointer types passing 'double *' to parameter of type
    'int *' [-Wincompatible-pointer-types]

This is the type system doing exactly what Chapter 5.5 said a prototype enables: checking that a call matches. A function written to read four bytes as an int has been handed the address of an eight-byte double, and the compiler can see it.

5. A Cast Silences It — Which Is the Danger

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    char *pc = (char *)&value;        /* accepted, no diagnostic */

A cast tells the compiler you meant it, and the warning disappears entirely.

That is appropriate in §2's program, where the whole point was to look at the same storage through a narrower type, and char * has a special standing in C for exactly that purpose — examining an object's bytes.

It is not appropriate as a way of making a warning go away. Chapter 3.12 made the general point about casts, and it applies with more force here: a cast between pointer types does not convert anything, does not check anything, and does not make the resulting use correct. It records that you have taken responsibility.

Before writing a cast between pointer types, say out loud what the object at that address actually is. If the answer is not the type you are casting to, the cast is hiding a bug rather than expressing an intent.

6. The Belief This Corrects

7. What You Should Be Able to Say Now

  1. An address says where; it does not say how many bytes or what they mean.
  2. A pointer type supplies both — int * is the address of an object that is an int.
  3. Two pointers can hold the same address and still read different amounts: sizeof *pi is 4 and sizeof *pc is 1.
  4. The type is also what makes it possible to move a pointer — Chapter 7.8.
  5. Mixing pointer types without a cast is a constraint violation, reported by clang as a warning by default.
  6. Promote it to an error with -pedantic-errors or -Werror=incompatible-pointer-types.
  7. That is the fourth recommended addition to the default warning set in this curriculum.
  8. A cast silences the diagnostic without fixing anything — it records responsibility, not correctness.
  9. char * is the legitimate tool for examining an object's bytes.
  10. All pointers being the same size is implementation behaviour, and the value being an address does not make the type redundant.

8. Exercises

Exercise 1 — say what each type means. Write out, in words, what each declaration says about the object the pointer identifies.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
int *a;
char *b;
double *c;
unsigned short *d;

Exercise 2 — predict the sizes. Assuming char is 1, short is 2, int is 4, double is 8 and pointers are 8, give each value and say which are guaranteed by C and which are implementation-defined.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
sizeof (int *)      sizeof *pi      sizeof (char *)     sizeof *pc

where pi is an int * and pc is a char *.

Exercise 3 — explain the difference. pi and pc in §2 hold the same number. Explain, in two sentences, how *pi and *pc can do different things, without using the word type.

Exercise 4 — classify the diagnostics. For each, say whether the compiler objects, what it says, and whether the program would still build at the curriculum's usual warning level.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
int i; char *p = &i;
int i; int *p = &i;
int i; char *p = (char *)&i;
double d; int *p = &d;

Exercise 5 — judge the cast. For each use of a cast between pointer types, say whether it expresses an intention or hides a bug, and why.

  • casting &some_int to char * in order to look at its bytes
  • casting &some_double to int * because the compiler complained
  • casting &some_int to int * when it is already an int *

Exercise 6 — find the real fault. A colleague has a function void log_value(int *v); and calls it as log_value(&temperature) where temperature is a double. They silence the warning with a cast and the program then prints a wrong number. Explain what the function reads, why the cast did not help, and what the two correct repairs are.

Exercise 7 — choose the flag. Write the compiler command you would use for a project where mixing pointer types must never reach a build, and explain which part of the command does that. Then list the other three flags this curriculum has recommended adding, with one line each.

Exercise 8 — argue the design. Explain to someone coming from a language with a single generic reference type why C has separate pointer types, using §2's sizeof result as your evidence. Then say what C's equivalent of their single type is, and the one restriction that comes with it.

9. Summary

  • An address alone is not enough — using an object needs a width and an interpretation.
  • The pointer type supplies both, and it is how *p knows what to do.
  • Pointers holding the same address can read different amounts: 4 bytes versus 1.
  • The type also enables moving a pointer, which is Chapter 7.8's.
  • Mixing pointer types is a constraint violation reported as a warning by default — promote it to an error.
  • A cast silences the diagnostic and fixes nothing; char * is the legitimate way to inspect bytes.
  • Equal pointer sizes are implementation behaviour, and the value being an address does not make the type redundant.
  • void * is the deliberate exception, and it is Chapter 7.12's.

Every pointer so far has been initialised from the address of a real object, which made every dereference in the last two chapters safe by construction. That will not survive contact with real code, where a pointer is routinely declared before there is anything to point at, handed back by a function that may have failed, or stored in a structure that was only partly filled in. C provides one value whose entire job is to say this pointer deliberately identifies no object, and the discipline that goes with it is the subject of the next chapter.

Standards & specifications

Governing standard
ISO/IEC 9899 (the C language standard)(opens ISO/IEC (JTC 1/SC 22/WG 14) in a new tab)

Defines the C language itself — the type system, expression semantics, object model, storage duration and the behaviour categories a conforming implementation must honour. It is a FAMILY of editions (C89/C90, C99, C11, C17, C23) and this curriculum names the edition wherever a claim depends on one. It does not define any foreign-function interface to a hardware description language, and it does not define platform behaviour: what a memory-mapped access does on real silicon is the implementation's and the device's contract, not this standard's. Working drafts published by WG14 are freely available and are what most engineers read day to day; a draft is not the published standard.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the C Programming curriculum.