For more blog posts, see the complete catalog of the romantic carriage of audio and video systems learning

Introduction to C Language

To learn audio and video development, first of all C, C++ is a necessary programming language, because many well-known audio and video libraries, such as FFMPEG, X264, etc., are written in C language, and we want to use these libraries must use C, C++ to develop programs.

What language is C? C language is a process – oriented compiled language, which runs very fast, second only to assembly language. C language is the core language of the computer industry, operating system, hardware drivers, key components, database and so on are inseparable from C language; If you don’t learn C language, you can’t understand the bottom layer of computer. Many later languages (C++, Java, etc.) reference C language, it is no exaggeration to say that C is the first modern programming language, it changed the world of programming.

The following are the features of C: 1. High Efficiency: C is an efficient language. C exhibits a level of fine-grained control normally reserved for assembly language, which is an internally formulated set of mnemonics for a particular CPU design. Different CPU types use different assembly languages. If you wish, you can fine-tune the program for maximum speed or maximum memory utilization.

2. Portability: C is a portable language. This means that C programs written on one system can run on other systems with little or no modification.

3. Powerful features and flexibility: C powerful and flexible. The powerful and flexible UNIX operating system, for example, is written in C. Many compilers and interpreters for other languages (Perl, Python, BASIC, Pascal) are also written in C, including the familiar JVM.

Of course, the content of C language is very large, and there is no need to talk about it comprehensively. After all, this series is about audio and video, and the readers should have the foundation of C language (at least other languages), so here I will only talk about some key points and difficulties of C language, some essential things.

Nature of variables

I believe that variables are familiar, but not everyone has thought carefully about the nature of variables.

First of all, what is the nature of the execution process. A program is a set of algorithms and data structures that manipulate data, and where is the data being manipulated, in memory, so when YOU write a program you have to specify what chunk of memory I’m going to manipulate, and how is that chunk of data going to be represented in the program? Just use its memory address, right? That is so inconvenient that programmers use variable names to refer to data in memory. Specifically, a variable is an alias for a contiguous memory space. Programs use variables to apply for and name memory space, and use variable names to access memory space. Instead of reading or writing data to a variable name, you read or write data to the memory space that the variable represents

We all know that a variable must have a data type, so what is the type of the variable?

A variable is an alias for a contiguous memory space. How much space is allocated to a variable? For example, to build a nest for animals, small animals can not live, make a big waste of space, dog kennel can not live for elephants, elephant kennel to live for dogs is really a waste, and, do not specify the size of memory, when the computer read data do not know how much scope to read. Therefore, the allocated memory size needs to be specified by the data type, or the data type is an alias for fixed size memory.

Is, of course, only the specified memory size is not enough, also need to be specified on the data in memory is said what kind of things, the concrete is said cat or dog or the elephant, only know the height and size does not know the specific animals, because the data in memory is stored in 2 base, so you need to use different representations (similar agreements) represent different data types, As the name implies, data types also distinguish between the type of data, such as whether the data is a character, integer, or decimal. So by using data types, let the computer know how to read the value of a variable.

Memory management

The program is run in memory, because of the characteristics of C language, so it is closer to memory than any programming language, and master its memory management mechanism, is also a top priority to learn c language, a lot of other problems can also be solved by memory analysis and derivation.

The memory model

We know that after the application program is started, it will be loaded into the Memory for execution. At this time, the CPU takes out data and instructions from the Memory to execute. We call the distribution of the address space in the program as the Memory Model. The memory model is determined by the operating system. The following is an example of the MEMORY model of C program in Linux 32-bit system:

The figure above clearly represents the memory model from the ground address to the high address partition, the following is the description of each major partition:

In general, program source code is compiled into two main segments: program instructions and program data. Code area belongs to store program instructions, constant area, global data area, heap area, stack area belongs to store program data. The program code area, constant area, and global data area are allocated as soon as the program is loaded into memory and remain in fixed size for the duration of the program, only to be reclaimed by the operating system after the program is finished. The stack area and heap area are dynamically opened up while the program is running.

So why separate program instructions from program data?

Once the program is loaded into memory, data and code can be mapped to two separate memory areas. Since the data area is readable and writable to the process and the instruction area is read only to the program, the program instruction area and the data area can be set to read and write or read only after partitioning. This prevents the program’s instructions from being modified, intentionally or unintentionally.

When the system running in more of the same programs, the program execution of instructions are the same, so only need to save a copy of the program in the memory is ok, and then these programs can share this code instructions, just every procedure in the operation of the data is different, so can save a lot of memory.

The following is a detailed analysis of the major partitions:

Global data area

Stores variables defined outside a function that can be accessed globally (other files).

The constant area

A variable that stores string constants and const modifications.

The stack

The stack is a tube of memory carried out by the system, and the data structure is the familiar stack structure (first in, last out). Mainly store function parameters and local variables. After the function completes execution, the system releases the stack area of memory itself, without the need for programmer management. When a function is called, it pushes information related to the function, such as parameters, local variables, return address, saved context, and so on. When the function is finished, this information is destroyed. Therefore, local variables and parameters are only valid in the current function. They cannot be passed outside the function because their memory is gone. In addition, the stack is thread private.

As can be seen from the figure above, “Memory model for C programs in Linux 32-bit systems”, the stack grows from high addresses down.

When a function is called, the distribution of stack memory is shown in the figure below. It can be seen that the function itself opens a stack, and the function stacks are also organized by a structure similar to stacks. In the figure, the EBP pointer points to the bottom of the stack of the calling function, and the ESP pointer points to the top of the stack of the calling function. To increase the memory space of the current function stack, simply move ESP. Here each function stack keeps its own pointer to the bottom of the stack so that after the next function stack is reclaimed, the EBP can refer to the bottom of its own function stack for recovery. (Not exactly the same as the stack generated by the compiler in different compilation modes)In fact, when the program starts, it allocates an appropriately sized chunk of memory to the stack area. This is sufficient for ordinary function calls. Functions in and out of the stack are just transformations pointing to eBP or ESP registers, or writing data to existing memory, and there is no memory allocation or release involved.We often hear that “stack memory is allocated more efficiently than heap” for this reason, because in most cases stack memory is not actually allocated, just operations on existing memory.

The heap

The heap is manually applied and released by the programmer. If it is not released manually, the program will be recycled by the system after the end of the program. The life cycle is the entire program running period. More importantly, the heap is a large container, and its capacity is much larger than the stack, which can solve the memory overflow problem. Apply for the heap using malloc or new. The heap is completely controlled by the programmer (and is the only area of memory that the programmer has complete control over), allocating as much as they want and freeing as they want, which is very flexible, but also introduces the problem of memory leaks. The heap, though flexible, is less efficient than the stack.

Specific code example description

#include <stdio.h>
char *str1 = "c.biancheng.net";  String in constant area, str1 in global data area
int n;  // Global data area
char* func(a){
    char *str = "C";  // String in constant area, STR in stack area
    return str;
}
int main(a){
    int a;  / / the stack area
    char *str2 = "01234";  // String in constant area, str2 in stack area
    char arr[] = "hello world!"; // Note that the string stored in the character array is not a constant, it is readable and writable
    char  arr[20] = "56789";  // String and arR are in the stack
    char *pstr = func();  / / the stack area
    int b;  / / the stack area
    int *ip = (int*)malloc(N * sizeof(int) // IP is stored in the heap
    return 0;
}
Copy the code

Memory allocated by the function stack is freed after the function executes:

char* testStack(a){
    char p[] = "hello world!"; // The string is stored on the stack
    printf("in testStack %s\n", p);
    return p;
}

int main(a) {
    char* p = NULL;
    p = testStack();
    printf("out of testStack %s\n",p); // Returns the address of the string in the function

    return 0;
}
Copy the code

In testStack Hello world! out of testStack (null)

Null indicates that the referenced memory has been freed after the execution of the function.

But to point to a string:

char* testStack(a){
    char *p = "hello world!"; // Store in the constant area
    printf("in testStack %s\n", p);
    return p;
}
Copy the code

In testStack Hello world! out of testStack hello world!

Memory is not freed after the result of the function execution, confirming that the string pointed to by the character pointer is stored in the constant area.

Prove that stack growth is down (low address direction) :

void testStack1(a){

    int a = 10;
    int b = 20;
    int c = 30;
    int d = 40;

    printf("a = %d\n", &a);
    printf("b = %d\n", &b);
    printf("c = %d\n", &c);
    printf("d = %d\n", &d);
    
}
Copy the code

Running results: A = 6421996 B = 6421992 C = 6421988 D = 6421984

You can see that the addresses decrease, that is, the stack grows from high to low addresses.

Prove that a heap variable will not be released without manual release:

int *get(a)
{
    int *p = (int *)malloc(sizeof(int));// A heap space is requested
    *p = 10;
    return p;
}

int main(a){
    int *x = get();
    printf("x: %d\n", *x);
}
Copy the code

Running result: x: 10

The get function returns the value of the pointer p, that is, the memory created by malloc. After the function is executed, the correct value of the pointer x is still obtained, indicating that the memory was not reclaimed after the function was called.

If free before p returns:

int *get(a)
{
    int *p = (int *)malloc(sizeof(int));// A heap space is requested
    *p = 10;
    free(p);
    return p;
}
Copy the code

Running result: X: 38870160

At this point, the returned pointer is a wild pointer, which has been freed to a random value.

Prove that static variables are global:

Static local variable = static local variable = static

int testStatic(a) {
    static int s = 0;
    s = s + 1;
    return s;
}

int main(a) {

    for (int i = 0; i<=10; i++){int a = testStatic();
        printf("a = %d\n", a); }}Copy the code

Running result: A = 1 A = 2 A = 3 A = 4 A = 5 A = 6 A = 7 A = 8 A = 9 a = 10 a = 11

When the testStatic function is executed, the static variable s is not released. Static variables are stored in the global variable area.

Memory alignment

The CPU accesses the memory through the address bus, and generally how many bits of data the CPU can fetch at a time is how many bits of data. Therefore, the 32-bit CPU can process 4 bytes of data at a time, so that 4 bytes of data are read from memory at a time, and the number of reads per unit time is called the dominant frequency. In order to achieve the fastest addressing, that is, retrieving as much data as possible at once and not repeating it,CPU addressing takes steps and addresses only memory numbered multiples of a certain amount of data (not necessarily strictly multiples of 4, if the initial address is not 0). For example, the address step of the 32-bit CPU is 4 bytes, that is, only the multiples of memory numbered 4 will be addressed each time, for example, only 0, 4, 8, 12… Address 1000 for the start of addressing:

Therefore, it is desirable for a program to have a variable within the range of one addressing step, so that the value of the variable can be read at once; If the step size is stored, you need to read twice and then concatenate the data, which is obviously less efficient.

Putting a piece of data as close to a step size as possible to avoid step size storage is called memory alignment.

If the initial address is 0, then the 32-bit CPU can read the data in four bytes at a time. But if the first address is 6, the 32-bit CPU needs to read four bytes from memory address 4 for the first time, four bytes from memory address 8 for the second time, and then concatenate the last two bytes from the first read and the first two bytes from the second read respectively. This clearly shows the impact of step size storage on read efficiency.

So the ** compiler automatically places a data as close to a step as possible to avoid step storage, which is called memory alignment. The ** alignment number depends on compilation mode, in 32-bit compilation mode, the default alignment is 4 bytes; In 64-bit compilation mode, 8 bytes are aligned by default.

The size returned by sizeof is the size allocated to the variable, not just the space it used. So it contains aligned memory.)

struct {
    int a;
    char b;
    double c;
} t = {10.'C'.20.1};

int main(a) {
	// Print out the size of each type in the current environment
    printf("int length: %d\n".sizeof(int));
    printf("char length: %d\n".sizeof(char));
    printf("double length: %d\n".sizeof(double));
	// Prints out the size of the structure
    printf("struct length: %d\n".sizeof(t));
    printf("&a: %X\n&b: %X\n&c: %X\n", &t.a, &t.b, &t.c);
    return 0;
}
Copy the code

Int length: 4 char length: 1 double length: 8

struct length: 16

&a: 408010 &b: 408014 &c: 408018

As you can see from the first three lines, the current compilation mode is 32-bit, so the addressing step is 4. So if the a, B, and C attributes in t are not memory aligned, the size of t should be 4+1+8=13, but the printed size is 16. Look at the starting address of a, B, and C in t, and notice that C is stored at address 408018 and B at address 408014. If c is not aligned, the start address of C should be 408015. Because there is no room for C in the current step, b is aligned with memory, and 3 bytes are added to make C start from the next step.

Suppose the structure is changed to:

struct {
    char a;
    short b;
    int c;
} t = {'C'.10.20};
Copy the code

Char length: 1 short length: 2 int length: 4 struct length: 8 &a: 408010&B: 408012&C: 408014

Note that b is still in the current step because the current step can still contain B after storing A. However, there is only one byte left in the current step after storing B, so there is no space for C, so a byte is added (see address output).

If the structure is changed to the following, memory alignment is not required, because the two short attributes are exactly one step full, and the following ints are exactly one step full:

struct {
    short a;
    short b;
    int c;
} t = {1.10.20};
Copy the code

Short length: 2 int length: 4 struct length: 8 &a: 408010&B: 408012&C: 408014

The results confirmed the above statement.

Discussion on C language pointer (1)