Explain the relationship between multidimensional arrays and pointers in detail

Let's first introduce a simple one-dimensional array:

Columns such as:

int a[3] = {0,1,2};

[3] and the type int clearly indicate how much memory the compiler should allocate for this stack, which is three int sizes!

The schematic in memory is:


From the perspective of the CPU, memory is a set of contiguous address spaces, so when we operate on a one-dimensional array, we only need to know the first address of the array, and we can obtain the address of each element in the memory by adding and subtracting the address offset. The virtual address of the data segment mapped from the file!

However, be careful not to cross the boundary. In fact, you will not have any problem accessing the space beyond the size of the array in your own address space, because you access it under your own address and will not be erased by the kernel card. Memory may be aligned, unknown unused data!

For detailed memory alignment, see: Detailed explanation of C language memory alignment

The method of use is also very simple:

int a[3] = { 0, 1, 2 };
	printf("%d", a[0]); //Print the 0th data

print result:


Using pointers:

Note that the array itself is an address, and the pointer can directly manipulate the address, so the pointer can be represented by an array:

int a[3] = { 0, 1, 2 };
	int *p = a;
	printf("%d", p[0]); //Print the 0th data

The compiler automatically assembles the dereference code based on the operator used by the expression!

print result:


It is also possible to explicitly use pointer dereferencing:

int a[3] = { 0, 1, 2 };
	int *p = a;
	printf("%d", *p+1); //Print the first data

Here p has pointed to the first address of the a array, that is, a[0]. If you want to print the value of the first element, you only need to dereference it and +1 to make it address offset by one type unit (int, compile The compiler will automatically perform the address offset add) on the assembly according to the combined type of the expression!

Two-dimensional array:

int a[3][3] = {{0,1,2},{3,4,5}}; //Define a two-dimensional array

The above expression is: define a two-dimensional array with 3 columns and 3 rows of data in each column!


The above is just an abstract expression, in fact, the bottom layer is a one-dimensional array:


The length is the set of each line, but it is more abstractly distinguished in the C language, how many segments it is divided into according to the value in the first [] operator! Determine how many bytes each segment of memory can store according to the [] after [], and determine what type of data the memory is used to store according to the type. Call alu (integer operator) or fpu (floating point operator) when performing operations.

Floating point numbers are stored in a separate way, so strict distinction is required!

And at the bottom, there is no type of distinction, all are binary numbers, so the compiler will check the read and write between types during the compilation phase, so the type restriction is maintained by the compiler!

Instructions:

int a[3][3] = { { 0, 1, 2 }, {3,4,5} };
	printf("%d", a[0][1]); //Print the first data of line 0

print result:


The following are the methods and problems of using pointers:

Let's take a look first:

Why does the following code throw an error?

 
 
int a[3][8] = {{1,2,3,4,5,6,7,8},{1,2,3,4,5,6,7,8},{1,2,3,4,5,6,7,8}};
int **p = a;

The reason is very simple. The second-level pointer can only be used to point to an int* pointer, and int a is a two-dimensional array. The actual type pointed to by the two types at the beginning is wrong. Secondly, the memory size occupied by the two sides is also different!

as

int a[3][8] occupies the size of int*3*8 bytes

while *p takes only 4 bytes (depending on compiler bitness)

Then the question comes again, why is a one-dimensional array okay?

The reasons for this are as follows:

In the C/C++ compiler, a one-dimensional array is implicitly a pointer. In other words, an array is a pointer, and the array itself is an address without secondary addressing. The difference between a pointer and an array is that the array does not need to be dereferenced, and the array The name is also a constant and cannot be assigned directly!

The most classic example is that when you take an array as a parameter, the compiler will automatically convert the array to a pointer, the reason is for the remaining memory!

The two-dimensional array is implicitly declared as: int *a[8];

So if we want to point to a two-dimensional array, we must declare it as int (*p)[4] = a;  //A pointer to a one-dimensional array with 7 integers is int *a[7];

If you don't believe me, let's modify the code to see:

 
 
int a[3][8] = {{1,2,3,4,5,6,7,8},{1,2,3,4,5,6,7,8},{1,2,3,4,5,6,7,8}};
int (*p)[5] = a; //here, change int (*p)[4] to int (*p)[5] to see what error will be reported

Report the following error:


You can see that: int a[3][8] is implicitly converted to: int(*)[8]!

repair it a little:

int (*p)[8] = a; //a pointer to an array of 8 integers;

Dereferencing method:

The simplest is that we can use the pointer directly as an array, because: the two-dimensional array is not actually converted to int (*)[8] is just an implicit type conversion, and the actual memory is still in the stack! (*p) points to: an implicit int *a, and int *a points to the first element of the first-dimensional array of a[3], which is the first address a[0]. It is necessary to know that the array addresses are continuous. Get the address of the next element by dereferencing the implicit *a+1! And the following [8] indicates how many elements there are in each one-dimensional array!

That is to say, int a[3][8] is implicitly converted to int *a[8], *a points to the first address of the original a[3], and the following [8] tells *a every time What is the offset of the elements!

That is to say [8] is 8 ints!

In fact, a more explicit representation is int a[3][8] = 3 int[8]

In fact, we don't need to dereference it, because the compiler using [] brackets will use the pointer as an array, and the array itself is an address, so the compiler will automatically convert the pointer to an address!

printf("%d", p[1][1]); //Print the first data of the first dimension

The above method is the simplest,

There is another way:

printf("%d", *(*(p+1)+1)); //Print the first data of the first dimension

Let's break down the above dereferencing process in detail.

First of all, the first step needs to dereference p. It is not used as an array here, so it needs to be dereferenced explicitly. As mentioned above, *p points to the implicit *a. Here, dereference it is actually found * a's address and +1 it

*(p+1)

The parentheses are added here because the * value operator has a higher priority than the + operator. Note that the multiplication * is not higher than the + operator, while the value * will be higher than the + operator. to determine the purpose of the *.

Let's look at p+1 again. As mentioned above, (*p) points to the implicit *a address, and *a points to the first address of the array, which is a[0], where p+1 is to let *a +1, add parentheses () to make it dereference the address by +1 first, otherwise it will dereference *a directly and then +1 the value of the element! That is, the address stored in the *a stack address is +1 instead of the real array address. If it is not dereferenced, it is the address of p itself +1!

Add a little knowledge:

The pointer also has its own address. The pointer exists in the stack. Generally, the stack memory of the pointer stores the heap or stack address!

Then add a parenthesis (*(p+1)) outside *(p+1), and finally dereference it +1 again: *(*(p+1)+1)

Let's explain in detail:

First, when we find the address of the implicit *a through *(p+1), note that only the address of the implicit *a is found, not the address of the array, we need to dereference *a again to find the *a stack memory storage The array address of:

The writing method of **p is the writing method to find the two-dimensional array in the form of a pointer!

If you don't believe me, let's try:

printf("%d", **p);

The print result is: 1

And **p+1 is +1 to the address of the array pointed to by a. You must know that two-dimensional arrays are actually one-dimensional arrays. All addresses are linearly sorted. All **p+1 points to the second element, not Parentheses are needed because ** has a higher priority than +. To calculate the expression according to this priority, it will first dereference p to find the implicit *a, and dereference *a to find the array address + 1, then the next element the address of:

printf("%d", **p+1);

Print result: 2

Through the above introduction, it should be easy to understand this code:

*(*(p+1)+1)

First, dereference *(p+1) to find the implicit *a and dereference its address, and then +1 it (here +1 is the offset address of int* byte size) also It is to find the *a offset address pointing to a[1], add 1 to it, that is, find the element in the array, and then dereference it, add parentheses before dereferencing, as mentioned above , the reason for the priority, otherwise it will find the first element of a[1] and then +1 to the value

So the correct way to write pointer references is:

*(*(p+1)+1)

Here's how to use a three-dimensional array:

Columns such as:

int nA [2] [2] [2];

It is not difficult to understand for such a three-dimensional array:

int nA [2] [2] [2];

In fact it is

Added one more line width per line

Columns such as:

int nA [2] [2] = {{1, 2}, {3, 4}};

After changing to a 3D array:

int nA[2][2][2] = { { { 1, 2 }, { 3, 4 } }, { { 5, 6 }, { 7, 8 } } }; // 3D array

A three-dimensional array can be regarded as a two-dimensional array, and a two-dimensional array can also be regarded as a one-dimensional array, because the addresses of the arrays in the computer are continuous, only rows and no columns, and a dimensional array is just an abstract expression!

3D is to add extra line width to each line


A more explicit expression is: int a[2][2][2] = 2 int[2][2]

A more explicit expression is actually: int a[2][2][2] = there are columns, each column has two rows, and each row can put 2 data!


Note that this is not a drawing, there is no height, so the three-dimensional expression at the lower level is actually increasing the line width for each line!

Instructions:

int nA[2][2][2] = { { { 1, 2 }, { 3, 4 } }, { { 5, 6 }, { 7, 8 } } }; // 3D array
	int (* p) [2] [2] = nA;
	printf("%d\n", p[0][1][1]); //Print the width of the first row and the first row of the 0th column

Note that the initialization of 3D must be enclosed in {}!


That means the width of each line


print result:


You can see that the data of the first element in the first row, the first row, the first row, and the first row is printed: 4


Stack subscripts start at 0 so the index is 1!

The following describes how to use the form of pointers to access:

int nA[2][2][2] = { { { 1, 2 }, { 3, 4 } }, { { 5, 6 }, { 7, 8 } } }; // 3D array
	int (* p) [2] [2] = nA;
	printf("%d\n", *(*(*p)+1)); //print column 0, line 0, line 1 width

print result:


Let's explain the above pointer dereference process:

*(*(*p)+1)

The first dereference of *p is to dereference the *nA pointer pointed to by p to find the *nA pointer. In *dereference, it is to find the first address dereference of nA[2] pointed to by *nA. Note that at this time, it must be dereferenced again. Because the line width has been divided into two, nA[2][2] has also been implicitly declared as a pointer **nA points to the first address of the array, which is the first address of nA[2][2], we To dereference it to determine which address to access***p This dereference method is to access the 0th element of the 0th row of the nA element, the 0th column of the 0th element, if +1 is to the 0th row of the 1st element The 0th element of the column accesses ***p+1. If you want to access each element in it, you need to perform parenthesis priority operation, as mentioned above:

(*p) dereference *nA

*(*p) Dereference the first address of the array element pointed to by *nA

*(*(*p)) As mentioned above, nA[2][2] has been implicitly declared as a pointer to point to each line width, so this step is to dereference the pointer and the first line of each line address

*(*(*p)+1) Perform addition and subtraction operations on the pointer, so that the pointer is offset to the next address by one pointer variable unit, which is the size of an int, and points to the next element

So what prints is:

Row 0, Column 0, 1st element: 2


If you want to print the 0th element of the 0th row and the 1st column, you only need to do *p+1

*(*(*p+1))

 


There are many pointer concepts and it is easy to be confused. The following are the declaration methods of several pointers:

1. An integer number;

int a;

 

2. A pointer to an integer;

int *a;

 

3. A pointer to a pointer, which points to a pointer to an integer;

int **a;

 

4. An array of 10 integers;

int a[10];

 

5. An array of 10 pointers that point to an integer;

int *a[10];

 

6. A pointer to an array of 10 integers;

int (*a)[10];

 

7. A pointer to a function that takes an integer parameter and returns an integer;

int (*a)(int);

 

8. A pointer to an array with 10 pointers, each of which points to an integer;

int *(*a)[10];

 

9. An array with 10 pointers, give the pointer to a function, the function has an integer parameter and returns an integer;

int (*a[10])(int);

 

10. A pointer to a function that has an integer parameter and returns a pointer to a function, and the returned function pointer points to a function that has an integer parameter and returns an integer;

int (*(*a)(int))(int);

In fact, there is no essential difference between a pointer and an array. The difference is that the array cannot be operated as an rvalue after initialization, to modify the size of the array or to point to other array addresses, so why is an array called an array or an address? Because the array is a constant after the declaration, its address is the starting address of the entire array, and the pointer can be pointed at will, of course, except for the pointer modified by the const modifier!

Moreover, the array name cannot participate in the operation, and the element to be involved in the operation must be indicated by the subscript display!

Then there is another question. The array name mentioned above is the first address of the array, so why use [] to indicate the subscript before it can be calculated?

Answer: Because the C/C++ compiler stipulates that although the array name is the first address, it can only be used as an rvalue operation. If you want to participate in the operation as an lvalue, you must display the specified subscript to determine which element to operate, and the array name corresponds to The first address of the entire array. If the operation on the array name does not specify which element to operate on, that is, the operation on the entire array, then for the compiler, if the array is larger than the number of CPU bits, it will cause a hardware interrupt!

Detailed explanation of CPU addressing: In -depth understanding of "CPU internal addressing mode"

Finally, I would like to add a little why to use pointers often?

Answer: Pointers save memory, using pointers and allocating memory through malloc can save compiled memory, and the stack is also limited!

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325692191&siteId=291194637