Hello C -- pointers and functions

Posted by MrPotatoes on Mon, 21 Feb 2022 04:12:10 +0100

Pointers make a great contribution to the function of the function. Pointers can pass data to the function and allow the function to modify the data. The function of pointer to function mainly has two aspects: passing pointer to function and declaring function pointer.

1, Stack and heap of programs

The stack and heap of programs are the runtime elements of C language programs.

1. Program stack

Program stack is a memory area that supports function execution. It usually shares a memory area with the heap. Usually, the program stack occupies the lower part of the memory area and the upper part of the heap memory area. The program stack stores stack frames, which store function parameters and local variables. When calling a function, the stack frame of the function is pushed onto the stack, and a stack frame grows upward. When the function terminates, the stack frame of the function pops up from the program stack, and the memory used by the stack frame will not be cleaned up, but may be covered by the stack frame of another function pushed onto the program stack. Dynamically allocated memory comes from the heap, which grows downward. With the allocation and release of memory, the heap will be full of fragments. Although the heap grows downward, it is only a general direction, and the actual memory may be allocated anywhere on the heap.

2. Stack frame

Composition of stack frame:

A. Return address

The internal address of the program to be returned after the function is completed

B. Local data storage

Memory allocated for local variables

C. Parameter storage

Memory allocated for function parameters

D. Stack pointer and base pointer

The pointer used by the runtime system to manage the stack

The stack pointer usually points to the top of the stack, and the base pointer (frame pointer) usually exists and points to the address inside the stack frame

2, Passing and returning data through pointers

1. Passing data with a pointer

In the function, the pointer variable is used as a parameter to pass data. You can modify the data in the function. If you do not need to modify the data, the pointer variable is limited to const type. Typical function applications are as follows:

char *strcpy(char *dest, const char *src);

If a function passes data by value, the data cannot be modified in the function.

2. Return pointer

The return pointer needs to return a pointer of a data type.

Problems with returning pointer from function:

A. Returns an uninitialized pointer

B. Returns a pointer to an invalid address.

C. Returns a pointer to a local variable

D. Returns a pointer but does not free memory

The dynamically allocated memory returned from the function must be released after using the memory, otherwise it will cause memory leakage.

The pointer or local variable of the local data returned by the function is wrong. After the function returns, the stack frame of the local data will be popped up by the program stack, and the data saved on the stack frame is very easy to be overwritten by the stack frame of the subsequent call function. By declaring local variables as static type, the scope of local data and variables can be limited within the function, but allocated outside the stack (data segment), so as to avoid local data and variables being overwritten by the stack frame of other functions.

3. Pointer passing pointer

When you pass a pointer to a function, you pass the value of the pointer variable. If you need to modify the original pointer instead of a copy of the pointer variable, you need to pass the pointer of the pointer.

4. Evaluation order of function parameters

The evaluation order of function parameters depends on the implementation of the compiler.

GCC compiler

#include <stdio.h>

void fun(int i, int k)
{
    printf("%d %d\n", i, k);
}

int main(int argc, char *argv[])
{
    int k = 1;
    int i = 1;
    int j = 1;
    int l = 1;
    j = j++ + j++;//j = 4
    l = ++l + ++l;//l = 6
    fun(++k,++k);//3,3
    fun(i++,i++);//2,1
    return 0;
}

Stack order of function parameters

When a function call occurs, the parameters will be passed to the called function, and the return value will be returned to the function caller.

Function calling conventions describe how function parameters are passed to the stack and how the stack is maintained.

The calling convention is used for library calling and library development.

Stack from right to left:__ stdcall, __cdecl, __thiscall

Stack from left to right:__ pascal, __fastcall

When C language calls the library functions of other languages, such as pascal language, the declaration calling convention needs to be displayed.

5. Variable parameter function

Functions with variable parameters can be defined in C language, and the implementation of variable parameter functions depends on stdarg H header file.

va_list: parameter set

va_arg: take the specific parameter value

va_start: identifies the start of the parameter

va_end: identifies the end of the parameter

Variable parameters must be accessed sequentially from beginning to end

At least one definite named parameter must exist in the parameter list

The variable parameter function cannot determine the actual number of parameters

Variable argument function cannot determine the actual type of argument, VA_ Argif the wrong parameter type is specified, the structure will be indeterminate.

#include <stdio.h>
#include <stdarg.h>

float average(int n, ...)
{
    va_list args;
    int i = 0;
    float sum = 0;

    va_start(args, n);

    for(i=0; i<n; i++)
    {
        sum += va_arg(args, int);
    }

    va_end(args);
    return sum / n;
}

int main()
{
    printf("%f\n", average(5, 1, 2, 3, 4, 5));
    printf("%f\n", average(4, 1, 2, 3, 4));

    return 0;
}

3, Function pointer

Function pointers are pointer variables that hold function addresses and pointer variables that point to function addresses.

1. Function type

In C language, functions have their own types. The type of function is determined by the return type, parameter type and number of parameters.

Function types can be renamed through typedef.

typedef type func(parameter list);

Code example:

#include <stdio.h>
typedef int func(int a);

int test(int a)
{
    printf("test\n");
}

int main(int argc, char *argv[])
{
    func *f;
    f = test;
    (*f)(1);
    return 0;
}

2. Declaration of function pointer

Use function type declaration:

typedef int func(int a);

fun *f;

The general format of function pointer declaration is:

Data type (* pointer variable name) ();

void * fun(void);//Function that returns a pointer of type void *
void (*pFun)(void);//Function pointer
void *(*pFun)(void *, void *);//Function pointer
typedef void(*Fun)(void);//Define a function pointer type func
Fun pfun = fun;//Assign the address of the function fun to the function pointer pfun
Fun pfun = &fun;//Assign the address of the function fun to the function pointer pfun
void *(*pf[5])(void);//Function pointer array

3. Use of function pointers

General process of calling function pointer:

A. Define function pointer variables

B. The entry address (function name) of the called function is assigned to the function pointer variable

C. Call a function as a function pointer variable

D. Function pointer variable form the general form of calling a function is: (* pointer variable name) (argument list)

Function calls and function pointer variables are called as follows:

fun();//function call
(*fun)();//Call a function as a function pointer variable
(**fun)();//Call a function as a function pointer variable
pfun();//Calling a function as a function pointer
(*pfun)();//Calling a function as a function pointer
(**pfun)();//Calling a function as a function pointer

Function pointer variables cannot be arithmetically operated, and the offset of function pointer is meaningless. The parentheses on both sides of (* pointer variable name) in a function call cannot be less. Among them, * should not be understood as evaluation operation, and * is just a representation symbol.

Function pointer is the key technology to realize callback function, which can realize the jump of fixed address in the program.

4. Transfer function pointer

The function pointer variable is passed as the parameter of the function, which makes the program code more flexible.

int add(int num1, int num2)
{
    return num1 + num2;
}

int sub(int num1, int num2)
{
    return num1 - num2;
}

typedef int (*pFun)(int, int);
int compute(pFun operation, int num1, int num2)
{
    return operation(num1, num2);
}

compute(add, 5, 6);
compute(sub, 10,2);

The addresses of the add and sub functions are passed to the compute function as parameters. Compute uses the addresses of the add and sub functions to call the corresponding operations.

5. Return function pointer

To return a function pointer, you need to declare the return type of the function as a function pointer.

int add(int num1, int num2)
{
    return num1 + num2;
}

int sub(int num1, int num2)
{
    return num1 - num2;
}

typedef int (*pFun)(int, int);

pFun select(char opcode)
{
    switch(opcode)
    {
    case '+':
        return add;
    case '-':
        return sub;
    }
}

int evaluate(char opcode, int num1, num2)
{
    pFun operation = select(opcode);
    return operation(num1, num2);
}

evaluate('+', 5, 6);
evaluate('-', 10, 6);

By entering one character and two operands, the corresponding calculation can be carried out

6. Function pointer array usage

The function pointer array can select the function to be executed based on some conditions. The function pointer array is declared as follows:

First statement:

typedef int (*operation)(int, int);
operation operations[128] = {NULL};

The second statement:

int (*operations[128]) (int, int) = {NULL};

After the function pointer array is declared, a certain type of operation function can be assigned to the array.

operations['+'] = add;
operations['-'] = sub;

int evaluate_array(char opcode, int num1, num2)
{
    pFun operation = operations[opcode];
    return operation(num1, num2);
}

evaluate_array('+', 6 ,9);

7. Function pointer conversion

Converts a pointer variable pointing to a function to another type of pointer variable. There is no guarantee that the function pointer and data pointer will work normally after being converted to each other.

4, Functions and macros

Macros are directly expanded by the preprocessor. The compiler does not know the existence of macros. Functions are entities directly compiled by the compiler, and the calling behavior is determined by the compiler. Multiple use of macros will lead to the increase of the volume of executable programs. Functions are executed by jumping, and only one function body exists in memory. Macro has high efficiency and no calling overhead; Activity records will be recorded during function calls, which has call overhead.              

The efficiency of macros is higher than that of functions, but macros are text substitution, and parameters cannot be type checked. Therefore, functions that can be completed by functions must not use macros, and recursive definitions cannot be used in macro definitions.

Example code:

#include <stdio.h>
#include <malloc.h>

#define MALLOC(type, x)   (type*)malloc(sizeof(type)*x)
#define FREE(p)           (free(p), p=NULL)

#define LOG_INT(i)        printf("%s = %d\n", #i, i)
#define LOG_CHAR(c)       printf("%s = %c\n", #c, c)
#define LOG_FLOAT(f)      printf("%s = %f\n", #f, f)
#define LOG_POINTER(p)    printf("%s = %p\n", #p, p)
#define LOG_STRING(s)     printf("%s = %s\n", #s, s)

#define FOREACH(i, n)     while(1) { int i = 0, l = n; for(i=0; i < l; i++)
#define BEGIN             {
#define END               } break; }

int main()
{
    int* pi = MALLOC(int, 5);
    char* str = "D.T.Software";

    LOG_STRING(str);
    LOG_POINTER(pi);
    FOREACH(k, 5)
    BEGIN
        pi[k] = k + 1;
    END

    FOREACH(n, 5)

    BEGIN
        int value = pi[n];
        LOG_INT(value);
    END

    FREE(pi);

    LOG_POINTER(pi);

    return 0;
}

5, Recursive function

Recursion is an idea of division and autonomy in mathematics. Recursion needs a boundary. When the boundary conditions are not met, recursion continues; When the boundary conditions are satisfied, the recursion terminates.

Recursive function is a self calling function in the function body. It is the application of recursive mathematical thought in program design. A recursive function must have a recursive exit. A function without a recursive exit will lead to infinite recursion and cause the program stack to overflow and crash.

General representation of recursive model:

1. Recursive implementation of string length function

int strlen_r(const char* s)
{
    if( *s )
    {
        return 1 + strlen_r(s+1);
    }
    else
    {
        return 0;
    }
}

2. Fibonacci sequence solution

Fibonacci sequence is expressed as follows: 1, 1, 2, 3, 5, 8, 13, 21

int fac(int n)
{
    if( n == 1 )
    {
        return 1;
    }
    else if( n == 2 )
    {
        return 1;
    }
    else
    {
        return fac(n-1) + fac(n-2);
    }

    return -1;
}

3. Recursive implementation of Hanoi Tower

Hanoi Tower problem

Move the wood block from A-pillar to C-pillar with the help of B-pillar

Only one piece of wood can be moved at a time

Small pieces of wood can only be placed on top of large pieces of wood

resolvent:

Move n-1 wood blocks from A-pillar to B-pillar with the help of C-pillar

Move the lowest wood block directly to the C-pillar

Move the n-1 song wood block on the B-pillar from the B-pillar to the C-pillar with the help of the A-pillar

#include <stdio.h>

void han_move(int n, char a, char b, char c)
{
    if( n == 1 )
    {
        printf("%c --> %c\n", a, c);
    }
    else
    {
        han_move(n-1, a, c, b);
        han_move(1, a, b, c);
        han_move(n-1, b, a, c);
    }
}

int main()
{
    han_move(3, 'A', 'B', 'C');

    return 0;
}

6, Function design principle

General principles of function design:

A. Function is a module with independent functions

B. The function name should reflect the function of the function to a certain extent

C. The function parameter name should reflect the meaning of the parameter

D. Try to avoid using global variables in functions

E. When the function parameter should not be modified in the function memory, the parameter should be declared with const

F. If the parameter is a pointer and is used only as an input parameter, the parameter should be declared with const

G. The function return value cannot be omitted. If there is no return value, the bit void should be declared

H. Checking the validity of parameters is very important for checking the validity of pointer parameters

1. Do not return a pointer to the stack memory. The function in the stack will be released automatically at the end of the function

J. The size of the function should be small and should be controlled within 80 lines as far as possible

K. The function avoids having too many parameters and is controlled within 4 adopted numbers

Topics: C C++