The declarator names the variable and can include a type modifier. For example, if declarator represents an array, the type of the pointer is modified to be a pointer to an array. You can declare a pointer to a structure, union, or enumeration type before you define the structure, union, or enumeration type.
In mathematics, an unary operation is an operation with only one operand, i.e. a single input. This is in contrast to binary operations, which use two operands. An example is any function f : A → A, where A is a set.
These are the type of operators that act upon just a single operand for producing a new value. All the unary operators have equal precedence, and their associativity is from right to left. When we combine the unary operator with an operand, we get the unary expression.
The unary operators require only one operand; they perform various operations such as incrementing/decrementing a value by one, negating an expression, or inverting the value of a boolean. The increment/decrement operators can be applied before (prefix) or after (postfix) the operand.
The reason why the shorthand:
int *bar = &foo;
in your example can be confusing is that it's easy to misread it as being equivalent to:
int *bar;
*bar = &foo; // error: use of uninitialized pointer bar!
when it actually means:
int *bar;
bar = &foo;
Written out like this, with the variable declaration and assignment separated, there is no such potential for confusion, and the use ↔ declaration parallelism described in your K&R quote works perfectly:
The first line declares a variable bar
, such that *bar
is an int
.
The second line assigns the address of foo
to bar
, making *bar
(an int
) an alias for foo
(also an int
).
When introducing C pointer syntax to beginners, it may be helpful to initially stick to this style of separating pointer declarations from assignments, and only introduce the combined shorthand syntax (with appropriate warnings about its potential for confusion) once the basic concepts of pointer use in C have been adequately internalized.
For your student to understand the meaning of the *
symbol in different contexts, they must first understand that the contexts are indeed different. Once they understand that the contexts are different (i.e. the difference between the left hand side of an assignment and a general expression) it isn't too much of a cognitive leap to understand what the differences are.
Firstly explain that the declaration of a variable cannot contain operators (demonstrate this by showing that putting a -
or +
symbol in a variable declaration simply causes an error). Then go on to show that an expression (i.e. on the right hand side of an assignment) can contain operators. Make sure the student understands that an expression and a variable declaration are two completely different contexts.
When they understand that the contexts are different, you can go on to explain that when the *
symbol is in a variable declaration in front of the variable identifier, it means 'declare this variable as a pointer'. Then you can explain that when used in an expression (as a unary operator) the *
symbol is the 'dereference operator' and it means 'the value at the address of' rather than its earlier meaning.
To truly convince your student, explain that the creators of C could have used any symbol to mean the dereference operator (i.e. they could have used @
instead) but for whatever reason they made the design decision to use *
.
All in all, there's no way around explaining that the contexts are different. If the student doesn't understand the contexts are different, they can't understand why the *
symbol can mean different things.
It is nice to know the difference between declaration and initialization. We declare variables as types and initialize them with values. If we do both at the same time we often call it a definition.
1.
int a; a = 42;
int a;
a = 42;
We declare an int
named a. Then we initialize it by giving it a value 42
.
2.
int a = 42;
We declare and int
named a and give it the value 42. It is initialized with 42
. A definition.
3.
a = 43;
When we use the variables we say we operate on them. a = 43
is an assignment operation. We assign the number 43 to the variable a.
By saying
int *bar;
we declare bar to be a pointer to an int. By saying
int *bar = &foo;
we declare bar and initialize it with the address of foo.
After we have initialized bar we can use the same operator, the asterisk, to access and operate on the value of foo. Without the operator we access and operate on the address the pointer is pointing to.
Besides that I let the picture speak.
A simplified ASCIIMATION on what is going on. (And here a player version if you want to pause etc.)
The 2nd statement int *bar = &foo;
can be viewed pictorially in memory as,
bar foo
+-----+ +-----+
|0x100| ---> | 1 |
+-----+ +-----+
0x200 0x100
Now bar
is a pointer of type int
containing address &
of foo
. Using the unary operator *
we deference to retrieve the value contained in 'foo' by using the pointer bar
.
EDIT: My approach with beginners is to explain the memory address
of a variable i.e
Memory Address:
Every variable has an address associated with it provided by the OS. In int a;
, &a
is address of variable a
.
Continue explaining basic types of variables in C
as,
Types of variables:
Variables can hold values of respective types but not addresses.
int a = 10; float b = 10.8; char ch = 'c'; `a, b, c` are variables.
Introducing pointers:
As said above variables, for example
int a = 10; // a contains value 10
int b;
b = &a; // ERROR
It is possible assigning b = a
but not b = &a
, since variable b
can hold value but not address, Hence we require Pointers.
Pointer or Pointer variables :
If a variable contains an address it is known as a pointer variable. Use *
in the declaration to inform that it is a pointer.
• Pointer can hold address but not value
• Pointer contains the address of an existing variable.
• Pointer points to an existing variable
Looking at the answers and comments here, there seems to be a general agreement that the syntax in question can be confusing for a beginner. Most of them propose something along these lines:
You may write int* bar
instead of int *bar
to highlight the difference. This means you won't follow the K&R "declaration mimics use" approach, but the Stroustrup C++ approach:
We don't declare *bar
to be an integer. We declare bar
to be an int*
. If we want to initialize a newly created variable in the same line, it is clear that we are dealing with bar
, not *bar
. int* bar = &foo;
The drawbacks:
int* foo, bar
vs int *foo, *bar
).Edit: A different approach that has been suggested, is to go the K&R "mimic" way, but without the "shorthand" syntax (see here). As soon as you omit doing a declaration and an assignment in the same line, everything will look much more coherent.
However, sooner or later the student will have to deal with pointers as function arguments. And pointers as return types. And pointers to functions. You will have to explain the difference between int *func();
and int (*func)();
. I think sooner or later things will fall apart. And maybe sooner is better than later.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With