#09 (02/14/2024)

Pointer Manipulation

Address operator &

When a C compiler reads your source code, it maps each variable defined in the code to an appropriate location in RAM that holds the value of that variable.
#include <stdio.h>
int main()
{
  float a =20.0, b=50.0;
  float *pa, *pb;
  pa=&a; pb=&b;
  printf("%f %f\n", a, b);
  printf("%f %f\n", *pa, *pb);
  return 0;
}
Memory map of a fictitious machine:
Variable name Machine absolute address Contents
100
101
a 102 20.0
b 150 50.0
151
pa 200 102
pb 220 150
In this fictitious machine, the variable a is mapped to (absolute) memory location of 102 in RAM which holds the value of 20.0. Similarly, the variable b is mapped to memory location of 150 that holds the value of 50.0.
You can find out the memory location (address) that holds the value of a given variable by placing an & (ampersand, called the address operator) in front of the variable name. Thus in the example above, a represents 20.0 and &a represents 102.
Example
#include <stdio.h>
int main()
{
  int a=10;
  printf("Address of a=%d\n", &a);
  return 0;
}
You can use %p instead of %d to display the memory address in hexadecimal mode.
#include <stdio.h>
int main()
{
  int a=10;
  printf("Address of a=%p\n", &a);
  return 0;
}
Examine the following:
#include <stdio.h>
int main()
{
  int a[]={100,2,-56};
  printf("%p\n", &a[0]);
  printf("%p\n", &a[1]);
  printf("%p\n", &a[2]);
  return 0;
}
Note that the address of adjacent component is incremented by 4 bytes which implies that the C compiler stores an integer variable in 4 bytes.
#include <stdio.h>
int main()
{
  double a[]={100,2,-56};
  printf("%p\n", &a[0]);
  printf("%p\n", &a[1]);
  printf("%p\n", &a[2]);
  return 0;
}
In the example above, it is seen that the C compiler stores double precision (double) numbers in 8 bytes.

Pointer

A pointer is a variable just like a above. The only difference is that it stores the "address of another variable." So if pa is a pointer variable, the content of pa is not like 20.0 or 50.0 but like 102 or 150 in the example above.
You must declare a pointer variable just like you declare an integer variable.
#include <stdio.h>
int main()
{
  float a=20.0;
  float *pa;

  pa=&a;

  printf("%p\n", pa);
  printf("%p\n", &a);

  return 0;
}
In the program above, float *pa1 declares pa as a pointer. Note that the value that a pointer holds is always an integer (memory address location) so the type of a pointer (float in the example above) merely implies that the variable pointed by the pointer pa is of float type.
Sometimes within the program you may want to examine what values the pointer actually refers to, i.e. you want to know the content of the variable pointed by the pointer. Let's look at the following program:
#include <stdio.h>
int main()
{
  float a=20.0;
  float *pa;

  pa=&a;

  printf("%f\n", *pa);
  printf("%f\n", a);
  return 0;
}
As seen in the program above, * (asterisk - known as the de-referencing operator) before the variable works the same way as using a directly 2
So if pa is a pointer pointing to a floating variable a, the use of a and *pa within a program is identical:
#include <stdio.h>
int main()
{
  float a=20.0;
  float *pa=&a;

  a=30.0;
  *pa = 40.0;
  printf("%f\n", *pa);
  printf("%f\n", a);
  return 0;
}
This way, one can effectively change the content of a without directly mentioning a (more later).
The C language uses pointers extensively for the following reasons:
The disadvantage of using pointers includes
Run the following code to verify that *pa and a are the same.
#include <stdio.h>
int main()
{
  float a=10.0,b=20.5;
  float *pa, *pb;

  pa=&a; pb=&b;

  printf("Address of a=%p\n", &a);
  printf("Address of b=%p\n", &b);
  printf("\n");
  printf("Pointer pa=%p\n", pa);
  printf("Pointer pb=%p\n", pb);
  printf("\n");
  printf("Value pointed by pa=%f\n",*pa);
  printf("Value pointed by pb=%f\n",*pb);
  return 0;
}

Here is one of the biggest reasons to use pointers
Suppose you want to write a function that takes a variable and multiply 10 by that variable.
#include <stdio.h>

void tentimes(float a)
{
a = 10.0*a;
}

int main()
{

 float b=20;
 tentimes(b);
 printf(" b = %f\n", b);
 return 0;
}
This program will not work as intended.
In this example, suppose we want to write a function that takes two variables as arguments and exchange the values of the two variables. You might write a program like
#include <stdio.h>

void swap(float a, float b)
{
float tmp;
tmp=a;
a=b;
b=tmp;
}

int main()
{
float a=10.0, b=50.0;

printf("Old a = %f and old b = %f\n",a,b);

swap(a,b);

printf("New a = %f and new b = %f\n", a,b);

return 0;
}
Unfortunately this program does not work as expected either. The way C handles function arguments is such that when a function is called with an argument, a copy of the value of the argument is passed to the function and the actual argument variable is never altered. This is not a bug but a feature !!!
YOU CANNOT CHANGE THE VALUE OF ARGUMENTS PASSED BY FUNCTIONS. This is a feature of C. When a function is called in a C program, a copy of the argument is passed to the function (call by value) so that the argument variable is never changed.
So here is a corrected program:
#include <stdio.h>

void swap(float *pa, float *pb)
{
float tmp;
tmp=*pa;
*pa=*pb;
*pb=tmp;
}

int main()
{
float a=10.0, b=50.0;

printf("Old a = %f and old b = %f\n",a,b);

swap(&a,&b);

printf("New a = %f and new b = %f\n", a,b);

return 0;
}
In the example above, the addresses of variables, a and b, are passed as the arguments of the function swap. You can then access to the location (address) directly and alter the value at that location by de-referencing (*).
Another example:
#include <stdio.h>
void twice(float a)
{
  a = 2.0*a;
}

int main()
{
 float a=20.0;
 twice(a);
 printf("a= %f\n", a);
 return 0;
}
The function, twice, is supposed to double the argument variable so you expect that the program above prints 40. Unfortunately this is not the case.
Corrected program:
#include <stdio.h>
void twice(float *pa)
{
*pa = 2.0* *pa;
}
int main()
{
  float a=20.0;
  twice(&a);
  printf("a=%f\n",a);
  return 0;
}

Summary

Pointers and arrays

When an array is declared as
#include <stdio.h>

int main()
{
float a[3]={1.0, 2.0, 3.0};
return 0;
}
the compiler actually interprets "a" as a pointer and reserves appropriate memory space.
So an array "a" is actually a pointer to the 0-th element of the array while a[0], a[1] and a[2] act just like regular variables.
The content of a is the address which points to a[0].
Since it is a pointer, dereferencing the array name ("*a") will give the 0-th element of the array, i.e. a[0]. This gives us a range of equivalent notations for array access. In the following examples, a is an array.
Array access Pointer equivalent
a[0] *a
a[1] *(a+1)
a[2] *(a+2)

Array name (e.g. a) = Pointer
Array element (e.g. a[3])= Regular variable

So a[i] and *(a+i) are identical just like &a[i] and (a+i) are identical. An increment of a by i (i.e. a+i) means that the pointer is advanced to the memory location at a + i ×4 (for integer and float), not by i bytes.
pointer_index_array.jpg
Execute the following program and examine the result:
#include <stdio.h>

int main()
{
	float a[3]={1.0, 2.0, 3.0};
	printf("%f\n", *a);
	printf("%f\n", a[0]);
	printf("\n");
	printf("%f\n", *(a+2));
	printf("%f\n", a[2]);
        return 0;
}
Since an array is like a pointer, we can pass an array to a function, and modify elements of that array. Here is an example:
#include <stdio.h>
void twice(float *a)
{
	int i;
	for (i=0; i<3; i++)
		a[i]=2*a[i];
	}

int main()
{
	float b[3]={1.0, 2.0, 3.0};
	int i;
	twice(b);
	for (i=0; i<3; i++) printf("%f\n", b[i]);
        return 0;
}
The program above is to use a function that takes an array as an input and doubles all the elements in that array. When we discussed pointers, we noted that the only way to modify the argument in a function is to use a pointer, instead of the variable itself. This principle also works for arrays as the name of an array IS a pointer. Therefore, when passing an array to a function, you don't have to add "&" before the array name.
The following program does the same effect as the one above.
#include <stdio.h>
void twice(float a[3])
{
	int i;
	for (i=0; i<3; i++)
		a[i]=2*a[i];
	}

int main()
{
	float b[3]={1.0, 2.0, 3.0};
	int i;
	twice(b);
	for (i=0; i<3; i++) printf("%f\n", b[i]);
        return 0;
}

Summary



Footnotes:

1float* pa; is allowed too.
2 In C, the asterisk, *, is used in three different ways;
  1. Multiplication (a*b)
  2. Declaration of a pointer (int *a)
  3. De-referencing (*pa = 25.0)



File translated from TEX by TTH, version 4.03.
On 19 Feb 2024, 07:58.