Organize the story around the C language array a little

Basics

int array[5];

A declaration like the one above means an array of length 5, int type, variable name array.

A continuous area is always secured on the address. [^ 1]

array[0]Means "the value of the 0th element of array"&array[0]Means "the address of the 0th element of array". Also&array[0]Is simplyarrayCan also be written.

Based on the above, let's check the relationship between the array and the address with the following code.

#include <stdio.h>
int main(void){
    int array[5];
    int i;
    
    for(i=0; i<5; i++){
        printf("%p:%d\n", &array[i], array[i]);
    }
}

Output result


0x7ffc7887de60:2022170472
0x7ffc7887de64:32764
0x7ffc7887de68:0
0x7ffc7887de6c:0
0x7ffc7887de70:0

The execution environment is paiza.io. In this environment, the int type is 4 bytes, so you can see that the address is incremented by 4 bytes from `` `0x7ffc7887de60```. As an aside, the value pointed to by the address is undefined just by declaring it, so it contains a strange value. Also, the address and its value change each time it is executed.

Arrays and pointers

Arrays are reserved on contiguous addresses, so you can use pointers instead of array subscripts to access your data.

Taking into account the important property that "incrementing a pointer" "increments by the size of the type that the pointer points to", you can use the pointer to access the elements of the array as follows:

#include <stdio.h>
int main(void){
    int array[5];
    int *p;
    int i;

    for(i=0; i<5; i++){
        array[i] = i;
        printf("%p:%d\n", &array[i], array[i]);
    }
    
    printf("-----\n");
    p = array;  // p = &array[0]Same meaning as
    
    while(p != &array[5]){
        printf("%p:%d\n", p, *p);
        p++;  //Since the type pointed to by p is an int type, it increases by 4.
    }
}

Execution result


0x7ffe615468f0:0
0x7ffe615468f4:1
0x7ffe615468f8:2
0x7ffe615468fc:3
0x7ffe61546900:4
-----
0x7ffe615468f0:0
0x7ffe615468f4:1
0x7ffe615468f8:2
0x7ffe615468fc:3
0x7ffe61546900:4

However, it is better not to use such pointer operations unless you have a specific reason. Pointer operations are generally confusing, and people who write C language like me avoid pointer operations as much as possible.

It's a shame, but let's rewrite `p ++` in the above code as `` `p + = 1``` and execute it. Everything else is the same.

#include <stdio.h>
int main(void){
    int array[5];
    int *p;
    int i;

    for(i=0; i<5; i++){
        array[i] = i;
        printf("%p:%d\n", &array[i], array[i]);
    }
    
    printf("-----\n");
    p = array;
    
    while(p != &array[5]){
        printf("%p:%d\n", p, *p);
        p += 1;  //What if you add 1 to p?
    }
}

Execution result


0x7ffcf23c63c0:0
0x7ffcf23c63c4:1
0x7ffcf23c63c8:2
0x7ffcf23c63cc:3
0x7ffcf23c63d0:4
-----
0x7ffcf23c63c0:0
0x7ffcf23c63c4:1
0x7ffcf23c63c8:2
0x7ffcf23c63cc:3
0x7ffcf23c63d0:4

Even though 1 is added, 4 is added. It's a strange pointer.

Pass an array as a function argument

Unfortunately, in C, you can't pass an array as a function argument.

However, when I use memset () and memcpy (), I usually pass an array.

char array[] = "0123456789";
memset(array, '*', 5);

As I wrote earlier, array has the same meaning as `` `& array [0] ```. In other words, what you are passing to memset () is not the array, but the "address of the 0th element of the array".

Based on the above, let's first check the behavior of the sizeof () operator as follows.

#include <stdio.h>

int print_size(int *array){
    printf("%p:%d\n", array, (int)sizeof(array));
}

int main(void){
    int array[10];

    printf("%p:%d\n", array, (int)sizeof(array));
    printf("-----\n");
    print_size(array);
}

Execution result


0x7ffc48040290:40
-----
0x7ffc48040290:8

The sizeof () operator is a operator </ b> that returns "the size it occupies in memory". Given a type to sizeof () returns the size of the type, and giving a variable returns the size that the variable occupies in memory. The reason for emphasizing that it is a operator </ b> is that you can give it an array. If you give an array, the size that the array occupies in memory is returned.

As a result, the `sizeof (array) ``` in the main () function returns 40```, while the ``` sizeof (array) in the print_size () function. It can be explained that ``` returns 8.

In other words, `sizeof (array)` in the main () function returns the size (int type * 10 = 40 bytes) that the array array occupies in memory, while in the print_size () function. `Sizeof (array)` returns the 8 bytes `` `of a pointer to a ```int type. The size of the pointer type is constant regardless of the pointing type (int type, char type, etc.), and is 8 bytes in this environment.

As an aside, I think that the misunderstanding that "an array can be passed to a function" is caused by the fact that formal parameters can be written as follows.

int print_size(int array[]){
   ...
}

I think this way of writing formal arguments like int array [] `` `strengthens the feeling of passing an array. On the other hand, if you write int * array```, it is clear that it is a pointer, so it is easier to understand if you write it like this.

Pass a two-dimensional array as a function argument

You can't pass an array as a function argument in the previous chapter, so we passed an address instead. The same is true for a two-dimensional array, when passing it as a function argument, pass the address.

#include <stdio.h>

void func(int (*array)[3]){
    int i, j;
    
    for(i=0; i<4; i++){
        for(j=0; j<3; j++){
            printf("%d ", array[i][j]);
        }
        printf("\n");
    }
}

int main(void){
    int array[][3] = {
        {1,2,3},
        {4,5,6},
        {7,8,9},
        {10,11,12},
    };
    
    func(array); //array is&array[0]Same as
    
    return 0;
}

Execution result


1 2 3 
4 5 6 
7 8 9 
10 11 12 

The problem is how to write formal parameters, write ```int (* array) [3] `` `. It means "a pointer to an int type array (3 elements)".

Store the function in an array

The correct answer is "store a pointer to a function in an array".

Before the main subject, let's talk about addresses.

The program we wrote is expanded in memory space. Variables are assigned addresses because they are expanded in memory space.

Addresses are assigned not only to variables but also to functions and string literals.

In C language, variable addresses are referenced using pointers. Similarly, the address of a function can be referenced using a pointer.

For example, the following is an example of executing the printf () function using a pointer.

#include <stdio.h>
int main(void){
    // int printf(const char * restrict format, ...);
    //So the pointer to printf is declared as follows
    int (*p)(const char * restrict format, ...);
  
    p = printf;
    
    printf("%p\n", printf);  //printf address
    printf("%p\n", p);       //Address of pointer p
    
    p("Hello World.\n");
}

Execution result


0x400440
0x400440
Hello World.

The idea is, "Since a function is assigned an address in memory space, create a pointer to the function and use that pointer to use the function."

Now that you know that you can execute a function using a pointer, let's store a pointer to the function in an array and call the function using the array.

#include <stdio.h>

enum State {
    START = 0,
    DO,
    FINISHED,
    END
};

enum State func_start(enum State state){
    printf("func_start\n");
    state = DO;
    return state;
}
enum State func_do(enum State state){
    static count = 0;
    printf("func_do\n");
    if(count++ == 3){
        state = FINISHED;
    }
    return state;
}
enum State func_finished(enum State state){
    printf("func_finished\n", state);
    state = END;
    return state;
}

int main(void){
    /*An array of pointers to functions of type enum State that return an enum State type
Stores the address of the function and allows the function to be executed using this array
    */
    enum State (*func_table[])(enum State) = {
        func_start,
        func_do,
        func_finished,
    };
    enum State state = START;

    while(1){
        if(state == END){
            break;
        }
        state = func_table[state](state);
    }
    return 0;
}

Output result


func_start
func_do
func_do
func_do
func_do
func_finished

I couldn't think of a good example, so the code is appropriate, but the technique of storing functions in this array is used instead of writing a long switch case. Personally, I used it when I wanted to realize something like a state machine or reduce the program area in an embedded system.

Arrays and string literals

A common mistake when initializing an array is to write:

#include <stdio.h>

int main(void){
    char str1[] = "abc";  //Correct
    char *str2 = "abc";   //Wrong
    
    printf("%s\n", str1);
    printf("%s\n", str2);

    return 0;
}

Output result


abc
abc

str1 is an array and has been successfully initialized.

Since str2 is a pointer to the char type, it is a wrong way to initialize the array. str2 refers to the address of the string literal "abc". String literals are assigned an address in memory space, so str2 refers to that address.

I will actually display the address and check it.

#include <stdio.h>

int main(void){
    char str1[] = "abc";
    char *str2 = "abc";    
    
    printf("str1:%p\n", str1);  //Show str1 address
    printf("str2:%p\n", str2);  //Show str2
    printf(" abc:%p\n", "abc"); // "abc"Display the address of
}

Execution result


str1:0x7ffdc1c8741c
str2:0x400614
 abc:0x400614

You can see that the address of the string literal "abc" is `` `0x400614``` and str2 points to it. On the other hand, str1 is assigned a separate address as an array.

Also, since string literals cannot be changed, an error will occur if you try to rewrite them.

str1[0] = 'x';
str2[0] = 'x';  //An error occurs

Referenced book

[Complete Conquest of C Language Pointer](https://www.amazon.co.jp/C%E8%A8%80%E8%AA%9E%E3%83%9D%E3%82%A4%E3%83% B3% E3% 82% BF% E5% AE% 8C% E5% 85% A8% E5% 88% B6% E8% A6% 87-% E6% A8% 99% E6% BA% 96% E3% 83% 97 % E3% 83% AD% E3% 82% B0% E3% 83% A9% E3% 83% 9E% E3% 83% BC% E3% 82% BA% E3% 83% A9% E3% 82% A4% E3 % 83% 96% E3% 83% A9% E3% 83% AA-% E5% 89% 8D% E6% A9% 8B-% E5% 92% 8C% E5% BC% A5 / dp / 4774111422)

[^ 1]: It means on the virtual address space.

Recommended Posts