This is the 7th day of my participation in the November Gwen Challenge. Check out the details: The last Gwen Challenge 2021

Pointer to the

This Pointer goes a little further than the Pointer in C, but it’s not all there is to it, as we’ll see later in the advanced section.

A pointer to define

Memory division

Memory is a large space consisting of small one-byte memory units, each of which is bound to an address, that is, the number of the memory unit. Like an ID number, an address uniquely identifies a memory location. Such as:

Pointers and pointer variables

The address points directly to another value stored in memory. Since the desired variable unit can be found through the address, the address points to a uniquely determined memory unit, so the address is visualized as a pointer.

We now define an integer variable a and allocate 4 bytes to it in memory. From this we can see that the essence of defining variables is to allocate space in memory. The address of the first byte of variable A is 0x0012FF40, which represents the address of variable A.

So what is a pointer variable? Now let’s define a “pointer” to a variable a.

We use &a to take out the address of variable A and put it in variable pa. Since pa holds the address, we use int * to define variable PA.

int * pa = &a; 
Copy the code

So pa is also a real variable in memory that stores the address number. Such variables are called pointer variables.

conclusion
  1. Pointers are addresses, and addresses are Pointers.
  2. Pointer variables are variables that hold addresses, and their contents are treated as addresses.

Pointer variables are often referred to simply as Pointers, and we need to distinguish between Pointers and pointer variables in context.

Pointer to the size
  • How big is a memory cell?
  • How are addresses numbered?

First, let’s analyze why the size of a memory cell is a byte.

For 32-bit machines, that is, 32 address lines, the electrical signals (positive/negative) generated by each address line at the time of addressing are converted into digital signals, with positive points being 1 and negative points being 0. In more general terms, it’s one if it’s powered on, and zero if it’s not.

So how many 01 combinations are there in 32 root address lines? High school permutation knowledge can show that there are 2322^{32}232 01 sequences. That’s going from 32 all zeros to 32 all ones.

00000000 00000000 00000000 00000000

00000000 00000000 00000000 00000001

… …

11111111 11111111 11111111 11111110

11111111 11111111 11111111 11111111

64-bit machines, of course, have 2642^{64}264 permutations.

Since we have 2322^{32}232 permutations on our 32-bit machine.

Each binary sequence is the serial number of a memory unit, then 2322 ^ {32} 232 memory units available, converted to decimal number is 4294967296 4294967296 4294967296.

If each memory cell is 1 bit size, then divide by 8, 536870912, 536870912, 536870912 byte, there were 524288, 524288, 524288 KB, Divide by 1024 and you get the familiar 512512512 MB, which is about half a GB. Isn’t it wasteful that a char variable needs 8 addresses?

If each memory cell is 1 byte, this translates to exactly 4 GB. That’s just fine. In the early days, we had one or two GIGABytes.

Pointer variables are used to store addresses. An address is 32 bits, so exactly four bytes are needed. So no matter what type, the size of the pointer variable is 4 bytes.

Of course, a 32-bit machine pointer is 4 bytes in size, and a 64-bit machine pointer is 8 bytes in size.

Pointer types

int a = 10;
int * pa = &a;
Copy the code
  • *On behalf ofpaIs a pointer
  • intOn behalf ofpaThe variable pointed to is of typeint

Variables have different types, and obviously pointer variables have different types. The size of a pointer variable is 4 bytes on a 32-bit platform, regardless of its type. It is reflected in two aspects, one is pointer dereference, and the other is pointer addition or subtraction of integers.

Pointers dereference aspects
int a = 0x11223344;
int* pa = &a;
*pa = 0; 
Copy the code

We create a variable a and point to it with a pointer pa, then dereference pa and set a to 0.

So let’s change the type of the pointer variable to int * pa and change it to char * pa.

The difference is that the pointer to int* accesses and modifies four bytes, while the pointer to char* modifies only one byte.

Pointer plus or minus positive

Now let’s add different types of Pointers to the same variable and add one to it. Such as:

You can see that the pointer to int* +1 skips backwards by 4 bytes and the pointer to char* +1 skips backwards by 1 byte.

conclusion

The pointer type determines:

  1. The number of bytes (memory size) that can be accessed during pointer dereference operations.
  2. Pointer to thePlus or minusAn integer can skip several bytes (steps).

This way, we can skip different bytes with different types of Pointers and access the contents of the variable more carefully. Such as:

int arr[10] = { 0 };
/ / 1.
int* pa = arr;
/ / 2.
char * pa = arr;
for (int i = 0; i < 10; i++)
{
    *(pa + i) = 1;
}
Copy the code

Two different Pointers bring different effects, as shown in the figure:

The first is to access the array elements one by one, and the second is to access the array character by character. Such as:

Wild pointer

Wild pointer definition

A pointer to an undefined position (random, incorrect, unbounded) is a wild pointer.

Incorrect location: indicates unallocated memory space, causing out-of-bounds access.

Origin of wild pointer
  1. Pointer is not initialized

     int* p;// Not initialized
     *p = 20;
    Copy the code
  2. Pointer access out of bounds

    int arr[10] = { 0 };
    int* p = arr;
    for (int i = 0; i <= 10; i++)// Cross border visit
    {
        *(p + i) = i;
    }
    Copy the code

Out of bounds ok, but not out of bounds access ^_^.

  • sample

     int* test(a){
     	int a = 10;
     	return &a;
     }
     int main(a){
     	int* p = test();
     	printf("%d\n", *p);// The wild pointer is out of bounds
     	return 0;
     }
    Copy the code

Here a is defined in the test function and will be destroyed when it is out of scope, so printing *p is out of bounds.

But our program still finds 10. Why is that?

The reason is that the a variable has not been destroyed by the operating system after its space is reclaimed, and the compiler reserves it once. And the argument is passed before the call, so *p is replaced with 10 before you call printf again.

  • Let’s modify it a little bit before printing*pIs called again before theprintfFunctions such as:
    int* test(a){
    	int a = 10;
    	return &a;
    }
    int main(a){
    	int* p = test();
    	printf("hehe\n");
    	printf("%d\n", *p);
    	return 0;
    }
    Copy the code

So this call to printf overwrites the space that was allocated to A, and it’s allocated to printf. The usage of the stack area is to press the stack (if you do not know, you can go to see the opening and destruction of the stack area space).

  • So if we print printf(“%d\n”, *p); *p = 20; Words such as:

    int* test(a){
     int a = 10;
     return &a;
    }
    int main(a){
     int* p = test();
     *p = 20;// Access illegal memory
     return 0;
    }
    Copy the code

The compiler detects that the space is illegal and reports an error.

  1. Pointer to space freed

As you can see from the above example, the pointer p points to the freed memory space previously occupied by the test function, which is also a very dangerous thing and is bound to be a wild pointer. The pointer to dynamically opened memory will also be free, which prevents it from becoming a wild pointer.

How to avoid wild Pointers
  1. Clear pointer initialization to determine the pointer

     int* p = &a;
     int* p =NULL;// NULL if you don't know where to point to
    Copy the code
  2. Beware of the pointer crossing the line

  3. Set to NULL immediately after a pointer to a space is released

  4. Avoid functions returning local variable addresses

  5. Checking pointer validity

Null Pointers are not dereferenced.

if(p ! =NULL){
    *p=20;// check that the pointer is not null
}
Copy the code

Assert (p) determines whether the pointer p is null and returns an error message.

Pointer arithmetic

Of course, pointer dereference is also a pointer operation, but we will consider only three classes here, after all, pointer dereference is a basic operation.

A pointer that adds or subtracts integers is still a pointer, just as a date added to days is still a date. A pointer minus a pointer is the number of elements, just as a date minus a date is the number of days.

Pointer to the+ -The integer
float values[N_VALUE];
float* vp = values;
for (vp = &values[0]; vp < &values[N_VALUE];)
{
    *vp++=0;
}
Copy the code

The code above iterates over zeros by means of Pointers to arrays.

  • In the circulatory body, VP comes after ++. Although ++ has a higher priority than *, post-+ + is used before ++.

    So it looks like the pointer is dereferenced and then ++.

  • The increment of a float pointer skips one float length and jumps to the next element.

    The pointer ++ skips the length of a type, regardless of the type.

  • When vp points to an address after the last element in the array, the loop ends.

    Although the address does not belong to the array, it is only used to judge the size of the condition (address has high and low), and does not access the content of the address, so it is not out of bounds access.

This example deals with operations on two Pointers: the pointer plus or minus integers, that is, the pointer ++. Pointer relation operation, pointer to each other than the size of the judgment condition.

A pointer plus an integer is a pointer that jumps back by an integer of type size in bytes.

As shown in the figure, pointer P-1 is the size by which pointer P jumps forward by one type.

Pointer addition or subtraction of an integer is when the pointer skips an integer byte of type size backwards or forwards.

Pointer to the-Pointer to the

Pointers can be subtracted to represent the “gap” between two addresses. Can we add Pointers to Pointers? The sum of two addresses is meaningless.

int arr[] = { 1.2.3.4.5.6.7.8.9.10 };
printf("%d\n", &arr[9] - &arr[0]);
Copy the code

What’s the answer to this question? Is it 36 or 9?

The answer is 9. The syntax says pointer – pointer, which yields the number of elements (subtracting subscripts) between two addresses.

Of course, the number of elements between two addresses can also be interpreted as the size of the bytes divided by the type size.

What if I were to do it in a different array? Such as:

int arr[] = { 1.2.3.4.5.6.7.8.9.10 };
char ch[] = { '1'.'2'.'3' };
printf("%d\n", &arr[9] - &ch[0]);
Copy the code

The compiler does not report an error because there are no syntax errors. But the resulting number is the number of elements. Is that an int or a char? So the numbers don’t make any sense.

So we get the premise of pointer subtraction: two Pointers point to the same space, like an array.

  • Pointer to the-The premise of a pointer is that two Pointers point to the same space.

  • Pointer to the-Pointer, the absolute value of the resulting number is the number of elements between two addresses.

Application: strlen function

int my_strlen(const char* s){
    char* begin = s;// mark the beginning
    while(*s++);//s = \0
    return s - begin - 1;// pointer subtraction
}
Copy the code
Pointer relation operation

Take the pointer plus or minus integer code example and modify it slightly.

/ / 1.
for(vp = &values[N_VALUE]; vp > &values[0];) { *--vp =0;
}
Copy the code

If you think of the space behind an array, you can also think of the contents of an array as being indexed by an array, because arrays are contiguous in memory. Traversing backwards, first — then dereferencing, will not result in an out-of-bounds access to the array. Let’s modify it a little bit:

/ / 2.
for(vp = &values[N_VALUE- 1]; vp >= &values[0]; vp--){ *vp =0;
}
Copy the code

For the last iteration, the pointer points to an address in front of values[0].

But we’ll try to choose the first method because the C standard allows a pointer to an array to be compared to a location in memory after the last element of the array, but not before the first element. As shown in figure:

The reason is that the compiler may store information about the array in front of it, such as the number of elements in the array. This may affect the operation of the program.

Pointer is also an address, address is a number is a number, you can compare the size. The relational operation on Pointers is to compare sizes.

Pointers and arrays

What’s the difference between Pointers and arrays? What’s the connection?

  • An array is a collection of elements of the same type that are stored in contiguous space. The size of an array depends on the element type and number of elements.

  • Pointer store address, is a variable. The size of the pointer is fixed to 4 (32bit) / 8 (64bit).

int arr[10] = { 0 };
printf("%p\n", arr);//0x0012ff40
printf("%p\n", &arr[0]);//0x0012ff40
Copy the code

The array name is the address of the first element in the array.

Ps: In both cases, the array name represents the entire array. In both cases, the array name represents the address of the first element.

  1. sizeof(arr)
  2. &arr

The name of the array can be stored as an address in a pointer variable, and we can access the array through a pointer.

In fact, when arrays are used as function parameters, they are reduced to Pointers. An entire array cannot be passed. But that’s another story.

int arr[10] = { 1.2.3.4.5.6.7.8.9.10 };
int* p = arr;
for (int i = 0; i < sz; i++)
{
    printf("&arr[%d] = %p <===> p+%d = %p\n", i, &arr[i], i, p + i);
}
Copy the code

In other words, p+ I is the address of the array arr subscript I, which is essentially the same thing.

The secondary pointer

As the name implies, the second level pointer is used to store the address of the first level pointer, through the second level pointer can also access the first level pointer.

  1. First we create a variable, A, which holds 10, so it has type int and address 0x0012FF40.

  2. It then takes the address of a and creates a new variable pa and stores & A in it, so it has type int* and address 0x004FFABC.

  3. Finally, a new variable ppa is created to store &p, so it is of type int** (secondary pointer).

You can find P by the address of P in ppa, and you can also find A by the address of A in P.

Indicates the meaning of * in the type

* in the gray box indicates that the variable is a pointer variable.

  • Level 1 pointerpIn front ofintsaidpObject to point toaisintType.
  • The secondary pointerppIn front ofint*saidppObject to point topIs of typeint*Type.
  • Level 3 pointerpppIn front ofint**saidpppObject to point toppIs of typeint**Type.
Multi-level pointer dereference operation
*p = 1;
* *pp = 2;
* * *ppp = 3;
Copy the code

As shown in the code above, we analyze level by level.

  • Logarithmic pointerpReference solution*pTo finda.
  • Pair pointerppReference solution*ppTo findp, and then dereference**ppTo finda.
  • Pair third order pointerpppReference solution*pppfindpp, and then dereference**pppTo findpUnreferenced again***pppTo finda.

So you can see that there are as many Pointers as there are references.

Pointer to an array

Pointer array definition

Before we answer the question of what a pointer array is, let’s look at what an integer array is and what a character array is.

int arr[10] = {0};
// Array of integers - An array of integer variables
char ch[10] = {'0'};
// Character array - An array of character variables
Copy the code

By analogy with an integer array and a character array, a pointer array is an array of pointer variables.

The types int and char before the array name indicate that the array element is of type int or char. So the type name before the pointer array name is either int* or char*. Such as:

// Int pointer array
int* parr[10];
// Array of character Pointers
char* pch[5];
Copy the code

For an integer pointer array, each element is the address of an integer variable. For a character pointer array, each element is the address of a character variable. The size of a pointer array depends only on the number of elements in the array.

Pointer array usage
int arr[] = { 10.20.30 };
int* parr[5] = {NULL};
/ / input
for (int i = 0; i < 3; i++)
{
    parr[i] = &arr[i];
}
/ / output 1.
for (int i = 0; i < 3 ; i++)
{
    printf("%d ", *parr[i]);
}
2 / / output.
for (int i  = 0; i < 3; i++)
{
    printf("%d ", **(parr+i));
}
Copy the code
  1. Remember to either initialize or specify the size. Pointer arrays remember that the content is initialized to a null pointer.
  2. When iterating through the array of pointer elements, remember to dereference them. With an array of+iWhen iterating through a set of elements, you resolve two layers of references.

At present, the corresponding pointer array is understood to this level, and we will learn the progression of Pointers later.