Pointer type and step size calculation

How to determine the type of pointer, which was explained in detail in the last article, now we will explain in detail how to calculate the step size of various types of pointer.

  • Common type of pointer step calculation

Pointers to normal types are type dependent, such as int takes 4 bytes (64 bits) and char takes 1 byte, so p++ memory addresses add 4 bytes.

	int a = 0;
	int* p = &a;
	printf("p:%p\n",p);//p:0000003DF72FF924
	p++;// The step length is 4 bytes
	printf("p++:%p\n",p);//p++:0000003DF72FF928
Copy the code
  • Step size calculation of multilevel Pointers

A multilevel pointer is just a pointer type that points to another pointer, and the variable size of a pointer type is fixed at 8 bytes, so the step size of a two-level pointer +1 is 8 bytes memory address + 8

	int** temp = &p;Int * is a pointer with a step of 8 bytes (pointer type is fixed to 8 bytes).
	printf("temp:%p\n",temp);//temp:0000003DF72FF948
	temp++;// The step length is 8 bytes
	printf("temp++:%p\n",temp);//temp++:0000003DF72FF950


	// Note: second-level Pointers can be typed strong
	char** c = &p;// This is ok
	printf("c:%p\n",*c);//c:000000F5B5AFF5C8
	printf("c:%d\n", **c);
Copy the code

As shown below:

  • Step size calculation of array Pointers

The step of a pointer to an array depends on the type of array, 4 if it’s an int, 1 if it’s a char

	// Pointers to arrays
	char arr[4] = { 'a'.'b'.'c'.'d' };
	// The pointer to the first address of the array is of type char
	char* p_arr = &arr;P_arr is a pointer to a char
	printf("p_arr:%p\n",p_arr);//p_arr:00000021DEB1F664
	p_arr++;// The step size of char is 1
	printf("p_arr++:%p,%c\n", p_arr, *p_arr);//p_arr++:00000021DEB1F665,b
Copy the code
  • Step size calculation of a pointer to a two-dimensional array

The array pointer type is: type (*p) [n] The pointer points to an array, so the step size depends on the length of the array and the type of the array, i.e., length * sizeof the array (type array type).

	// Array pointer +1 operates like a two-dimensional array as follows
	// A two-dimensional array
	int arr2[3] [4] = { 1.2.3.4.5.6.7.8.9.10.11.12 };
	/ / 1 2 3
	/ / 4 5 6
	/ / 7 8 and 9
	/ / 10 and 12
	int(*p_arr2)[3] = arr2;// Array pointer pointer type int()[4]
	printf("p_arr2:%p,%d\n", p_arr2, *p_arr2[0]);/ / p_arr2:00000008 b04ffbc8, 1
	p_arr2++;// The step is int * 4
	printf("p_arr2++:%p,%d\n", p_arr2, *p_arr2[0]);/ / p_arr2 + + : 00000008 b04ffbd4, 4
Copy the code

According to the following figure:

  • Step size calculation of pointer array

An array of Pointers is an array whose types are pointer types {int *,int *,int *….. } so the step size depends on the type to which the pointer points. This can be easily understood by looking at the following code

	// Array of Pointers
	int arr3[2] = {1.2};
	int arr4[2] = {3.4};
	int arr5[2] = {5.6};
	int arr6[2] = {7.8};
	int* p_arr3[4] = {arr3,arr4,arr5,arr6};// Array is int * int [2]
	printf("p_arr3:%p\n", p_arr3[0]);//p_arr3:0000006BC02FFC38

	p_arr3[0] + +;//int * and the pointer type is int so the step size is 4

	printf("p_arr3++:%p\n",p_arr3[0]);//p_arr3++:0000006BC02FFC3C
Copy the code

Distribution of memory

C language memory distribution: code area, constant to, global data area, stack area, heap area, dynamic link library. The memory distribution in C language is obviously different from that in Java. The MEMORY distribution in JVM is divided into heap area, stack area, and method area.

Kernel space and user space: In 32-bit environments, applications can theoretically have 4GB of virtual address space. Variables, functions, and strings in C all correspond to an area of memory. A portion of the 4GB memory address Space needs to be allocated to the operating system Kernel. Applications cannot directly access this portion of memory, which is called Kernel Space. Windows allocates 2GB of high address space to the kernel by default (or 1GB can be configured), while Linux allocates 1GB of high address space to the kernel by default. This means that an application’s 4GB address Space is left with only 2GB or 3GB of address Space, called User Space.

As shown in the following image: 0xffffFFFF is the high address and 0x00000000 is the low address. It can be seen from the memory model:

  1. The stack area grows from high addresses to low addresses and down
  2. The heap area is growing from a low address to a high address

For the stack area, the following code:

A > B > C high address -> low address, obviously in the main function stack the address of the line execution variable is higher than the address of the last execution variable.

int main(a) {
    int a = 1;
    int b = 2;
    int c = 3;
    // The high address is at the top of the stack and the low address is at the bottom of the stack, which means that the first address is larger than the last one
    // Address of A > B > C High address -> low address
    /* a:000000000061FE0C b:000000000061FE08 c:000000000061FE04 */
    printf("a:%p\n", &a);
    printf("b:%p\n", &b);
    printf("c:%p\n", &c);
    return 0;
}
Copy the code

Each function is equivalent to a stack frame in the stack area, and the stack area is removed after the function is executed.

The following heap area, the following code:

Heap area: Address from low to high is the opposite of stack area. Malloc allocates a memory space to the heap, and int occupies 4 bytes

    // Heap area: address from low to high is the opposite of stack area
    int *mp = malloc(16);
    for (size_t i = 0; i < 4; i++) {
        printf("mp %p\n", (mp + i));
    }
    /* mp 0000023891D455F0 mp 0000023891D455F4 mp 0000023891D455F8 mp 0000023891D455FC */
Copy the code

The execution process is as follows:

When a function is executed in C, it will be put on the stack and removed from the stack. For example, in the following code, the test function returns the result after the execution of the stack and then is removed from the stack

int test(a) {
    int a = 1;
    int b = 2;
    return a + b;
}
int main(a){
	int d = test();//test execution: main is pushed first to get the result
    return 0;
}
Copy the code

Consider: when there are too many functions, there will be frequent loading and unloading operations, how much will affect the performance, it is known that C performance is better than all languages, so why?

C language performance is strong, a direct pointer operation memory, another has a lot of optimization points, such as the above function frequently on the stack and out of the stack, C language has macro definition of the way, precompilation in the compilation stage will be replaced by the source code, and directly omit the operation of the stack and out of the stack.

The use of macro definitions is as follows:

ADD(1,2) is replaced by 1 + 2 at compile time. The call to Test is replaced with 99 at compile time

#define Test 99;// Macro variables are like static final in Java
#define ADD(x, y) x+y // Macro defines the addition operation to replace preprocessing at compile time

int main(a){
 	printf("add:%d\n", ADD(1.2));//ADD() is replaced directly with the corresponding source code 1+2 without frequent loading and unloading
    return 0;
}
Copy the code

Note: macro definitions look great, but there’s a problem: they don’t take precedence

In the following code, the expected output is 30, but the output is 21

	// macro definition problem
    printf("add:%d\n", ADD(1.2) *10);ADD(1,2) -> 1+2; 1+2*10 will only replace you with the corresponding source, regardless of priority
Copy the code

Therefore, when using macro definitions, it is important to use parentheses to ensure that the execution is prioritized so that the expected results can be obtained.

#define ADD(x, y) (x+y) // Macro defines the addition operation to replace preprocessing at compile time
printf("add:%d\n", ADD(1.2) *10);ADD(1,2) -> (1+2); - > (1 + 2) * 10
Copy the code
  • String pointer out of bounds problem, note in C language string encounter “\0” indicates the end
// The pointer is out of bounds
    char buf[3] = "abc";// The string is terminated when it encounters \0
    printf("buf:%s\n",buf);//abc??
    char buf2[3] = "ab\0";// The string is terminated when it encounters \0
    printf("buf2:%s\n",buf2);//ab
Copy the code
  • When the function is finished, the stack area will be removed (the variable is removed, but the memory area will not be cleared immediately), and the heap area needs to be freed manually.
 /** * the stack is removed when getArr is executed. * @return */
 int * getArr(a){
     int arr[4] = {1.2.3.4};
     return arr;
 }

 /** * arr refers to an area of memory in the heap. When getArr2 completes, the stack is removed but the heap is not removed * @return */
int * getArr2(a){
    int *arr = malloc(16);
    return arr;
}

	int * s1 = getArr();
    printf("s1:%p\n", s1);//s1:0000000000000000 Memory address cannot be found because the arR has been removed from the stack after execution

    int * s2 = getArr2();
    printf("s2:%p\n", s2);//s2:00000000007B1460
Copy the code
  • Complex use of secondary Pointers

The second-level pointer is a common pointer mode that must be mastered. The value of the second-level pointer points to the address of a pointer

Common secondary Pointers:

The secondary pointer here is declared in the stack area

    // Secondary pointer
    int b = 10;
    int *p = &b;
    int **p2 = &p;
Copy the code

So how do you declare a secondary pointer in the heap? The following code

void print_array(int **arr,int n){
    for (int i = 0; i < n; i++) {
        printf("arr:%d\n",*arr[i]);//arr[i] === **(arr+i)
        printf("arr:%d\n",**(arr+i));//arr[i] === **(arr+i)}}void test(a) {
    int a1 = 10;
    int a2 = 20;
    int a3 = 30;
    int a4 = 40;
    int a5 = 50;
    int n = 5;
    // Secondary Pointers are more complex to use
    int **arr = malloc(sizeof(int *) * n);
// arr[0] = &a1; //arr[0] === *arr
// arr[1] = &a2; //arr[0] === *(arr+1)
// arr[2] = &a3; //arr[0] === *(arr+2)
// arr[3] = &a4; //arr[0] === *(arr+3)
// arr[4] = &a5; //arr[0] === *(arr+4)

    *arr = &a1;//arr[0] === *arr
    *(arr+1) = &a2;//arr[0] === *(arr+1)
    *(arr+2) = &a3;//arr[0] === *(arr+2)
    *(arr+3) = &a4;//arr[0] === *(arr+3)
    *(arr+4) = &a5;//arr[0] === *(arr+4)
    print_array(arr,n);
    // Free up space
    free(arr);
}
Copy the code

The above code, via malloc, opens up a memory space that is the size of a pointer (64-bit 8 bytes) * N to store n pointer types.

As shown below: Model diagram of memory running

Note in the diagram: stack area from high address to low address, heap area from low address to high address

  • The common mode of second-level Pointers

Note: If a function wants to change the value of an external pointer, it passes the address of the pointer, not the address to which the pointer points (int p = &a, pass &p)*

void allocateSpace(int **addr){
    //p is a second-level pointer
    int *temp = malloc(sizeof(int) *16);
    for (int i = 0; i < 16; ++i) {
        temp[i] = 100+i;
    }
    *addr = temp;
}
void print_array(int **arr,int n){
    for (int i = 0; i < n; i++) {
        //(*arr) -> *p -> temp[]
        printf("arr:%d\n",(*arr)[i]); }}int main(a){
    // Level pointer
    int * p = NULL;
    // To change a pointer, pass the address of the pointer
    allocateSpace(&p);
    print_array(&p,16);
}
Copy the code

The code may look a little confused, look at the following drawing, once you understand:

AllocateSpace removes the stack frame from the stack area after the allocateSpace is executed.

If p and temp point to the same address, free(p) can be used to free the requested memory space. We can also write a generic release function using a secondary pointer:

void freeAddr(int ** addr){
    free(*addr);
    *addr = NULL;
}

freeAddr(&p);
Copy the code

The second-level pointer will be used very much in the actual application, so we must master the application of the second-level pointer. Understanding the second level Pointers in the above two memory model split diagrams becomes very simple.