Selected interview questions and answers for ZTE C/C++ linux background development in 2020

  1. The maintenance time complexity of the heap.
    If there are N nodes, then the height is H=logN, each parent node of the last layer only needs to be adjusted down at most once, the penultimate layer only needs to be adjusted down at most 2 times, the vertex needs to be adjusted down at most H times, and the parent of the last layer There are 2^(H-1) nodes in total, the penultimate layer has 2 (H-2) public and only 1 (20 ) vertices , so the total time complexity is s = 1 * 2^(H-1) + 2 * 2^(H-2) +… + (H-1) * 2^1 + H * 2^0 After substituting H into s = 2N-2-log2(N), the approximate time complexity is O (N).

  2. How does the CPU execute instructions?
    Each instruction executed by the computer can be divided into three stages. Instantly fetch instructions-----Analyze instructions-----Execute instructions.

The task of fetching instructions is to read the current instructions from the program memory according to the value in the program counter PC and send them to the instruction register.

The task of the analysis instruction stage is to decode the instruction opcode in the instruction register and analyze the nature of the instruction. If the instruction requires an operand, look for the operand address.

The process of the computer executing the program is actually repeating the above-mentioned operation process instruction by instruction, until it encounters a shutdown instruction and can wait for instructions in a loop.

When a general computer is working, the program and data must first be sent to the memory through the input interface circuit and the data bus through an external device, and then taken out and executed one by one. But the program in the single-chip microcomputer is generally solidified in the on-chip or off-chip program memory through the writer in advance. Therefore, commands can be executed as soon as the machine is turned on.

Below we will give an example to illustrate the execution process of the instruction:

When starting up, the program calculator PC becomes 0000H. Then the microcontroller automatically enters the process of executing the program under the action of the sequential circuit. The execution process is actually the cyclic process of fetching instructions (fetching the instruction stage stored in the memory in advance) and executing instructions (analyzing and executing instructions).

For example, the execution instruction: MOV A, #0E0H, its machine code is "74H E0H", the function of this instruction is to send the operand E0H to the accumulator,

74H has been stored in unit 0000H, and E0H has been stored in unit 0001H. When the microcontroller starts to run, it first enters the instruction fetching phase, the sequence is:

1 The content of the program counter (0000H at this time) is sent to the address register;

2 The content of the program counter automatically increases by 1 (changes to 0001H);

3 The content of the address register (0000H) is sent to the memory through the internal address bus, and the address in the memory is decoded to make the unit with the address 0000H selected;

4 CPU makes the read control line valid;

5 The content of the selected memory unit under the control of the read command (it should be 74H at this time) is sent to the internal data bus. Because it is the fetching phase, the content is sent to the instruction register through the data bus. At this point, the instruction fetching phase is completed, and the decoding analysis and instruction execution phase is entered.

Since the content in the instruction register this time is 74H (opcode), after the decoder is decoded, the microcontroller will know that the instruction is to send a number to the A accumulator, and the number is next to the code Storage unit. Therefore, to execute this instruction, the data (E0H) must be fetched from the memory to the CPU, that is, the second byte must be fetched from the memory. The process is very similar to the fetching phase, except that the PC is now 0001H. The instruction decoder combines the sequential components to generate a series of micro-operations of the 74H opcode, and the digital E0H is taken out from the 0001H unit. Because the instruction requires the obtained number to be sent to the A accumulator, the fetched number enters the A accumulator via the internal data bus instead of entering the instruction register. At this point, the execution of an instruction is complete. PC = "0002H" in the single chip microcomputer, the PC automatically adds 1 every time the CPU fetches an instruction or number from the memory, and the single chip microcomputer enters the next instruction fetch stage. This process has been repeated until a pause command is received or a loop waiting command is paused.

The CPU executes instructions one by one and completes all regulations.

  1. What function cannot be declared as a virtual function?
    Common functions that cannot be declared as virtual functions are ordinary functions (non-member functions), static member functions, inline member functions, constructors and friend functions. The following will analyze these situations separately.

1) Ordinary functions (non-member functions) can only be overloaded, cannot be overridden, and cannot be declared as virtual functions. Therefore, the compiler will bind the function at compile time.

2) A static member function cannot be a virtual function, because a static member function has only one code for each class. All objects share this code. It is not owned by an object, so it is not dynamically bound. The necessity of setting.

3) Inline member functions cannot be virtual functions, because inline functions themselves are set up for direct expansion in the code and reduce the cost of function calls, while virtual functions are for objects to accurately perform their actions after inheritance , It is impossible to unify. Besides, inline functions are expanded at compile time, and virtual functions can be dynamically bound to functions at runtime.

4) The constructor function cannot be a virtual function, because the constructor function is originally created to explicitly initialize the object members. However, the virtual function is mainly used to correctly handle the object without fully understanding the details. In addition, virtual functions produce different actions on different types of objects. The objects have not been produced yet. How to use virtual functions to complete the actions you want to accomplish?

5) Friend function. The C++ language does not support the inheritance of friend functions, and there is no virtual function for functions without inheritance. Friend function is not a member function of the class and cannot be inherited. Therefore, friend functions cannot be virtual functions.

  1. Online CPU is bursting high, how do you find the problem?
    1. Top command: Linux command. You can view real-time CPU usage. You can also view the CPU usage in the most recent period of time.

2. PS command: Linux command. Powerful process status monitoring commands. You can view the current CPU usage of the process and the threads in the process. Sampling data belonging to the current state.

3. jstack: Command provided by Java. You can view the current thread stack operation of a process. According to the output of this command, you can locate the current running status of all threads of a process, running code, and whether it is deadlocked, etc.

4. pstack: Linux command. You can view the current thread stack operation of a process.

  1. Cao Cao went south to attack Liu Bei, Liu Bei sent Guan Yu to guard Jinzhou, and Guan Yu sent Zhang Fei to guard the city gate. Liu Bei sent Zhuge Liang to ask Sun Quan for help. Sun Quan sent troops to attack Cao Cao. Please draw a UML diagram.Insert picture description here

  2. How to insert and delete the red-black tree?
    insert:

(1) If the parent node is black, insert directly without processing

(2) If the parent node is red and the uncle node is red, the parent node and uncle node become black, and the ancestor node becomes red, and the node operation is converted to the ancestor node

(3) If the current node is the right node of the parent node, the left-hand operation is centered on the parent node

(4) If the current node is the left node of the parent node, the parent node becomes black and the ancestor node becomes red, and the ancestor node is the center of the right rotation operation

delete:

(1) First, delete the current node according to the method of sorting the binary tree, and move to the next node if it needs to be transferred

(2) The current node must be such a situation: there is no left subtree.

(3) Deleted as a red node, no need to deal with, just follow the same as deleting binary tree node

(4) If the sibling node is black and the two child nodes of the sibling node are black, the sibling node will be changed to red, and the coloring will be transferred to the parent node

(5) If the sibling node is red, set the sibling node to black, and the parent node to red node, and perform left-hand operation on the parent node

(6) If the sibling node is black, the left child is red, and the right child is black, right-rotate the sibling node

(7) If the sibling node is black and the right child is red, assign the color of the parent node to the sibling node, set the parent node to black, set the right child of the sibling node to black, and rotate the parent node to the left

  1. An important part of the Linux operating system? A
    Linux system generally has 4 main parts: kernel, shell, file system and application. The kernel, shell, and file system together form the basic operating system structure. They allow users to run programs, manage files, and use the system.

1. The Linux kernel is the core of the operating system and has many basic functions, such as virtual memory, multitasking, shared libraries, demand loading, executable programs, and TCP/IP network functions. The Linux kernel module is divided into the following parts: storage management, CPU and process management, file system, device management and driver, network communication, system initialization and system call, etc.

2. The Linux shell is the user interface of the system and provides an interface for the user to interact with the kernel. It receives the command entered by the user and sends it to the kernel for execution. It is a command interpreter. In addition, the shell programming language has many characteristics of ordinary programming languages, and shell programs written in this programming language have the same effects as other applications.

3. Linux file system The file system is an organization method for storing files on storage devices such as disks. The Linux system can support a variety of currently popular file systems, such as EXT2, EXT3, FAT, FAT32, VFAT and ISO9660.

4. Linux application standard Linux systems generally have a set of assemblies called applications, which include text editors, programming languages, XWindow, office suites, Internet tools, and databases.

  1. If you call memset(this, 0, sizeof(*this)) in the constructor to initialize the memory space, is there any problem?
    For classes with virtual functions and virtual tables, data and functions inherited from virtual functions and virtual base tables cannot be called after memset

  2. What about TCP’s nagle algorithm and delayed ack, and CORK? What are their benefits? What is the effect of using together? What do you think can be improved?
    Nagle algorithm: Prevent network congestion caused by too many small packets in the network

Delay ack: reduce frequent sending of ACK packets

CORK: Turn multiple packets into one packet to send, improve network utilization, and make the load rate larger

  1. What is a coroutine? A
    coroutine is a more lightweight existence than threads. Just as a process can have multiple threads, a thread can also have multiple coroutines.
    Insert picture description here

The most important thing is that the coroutine is not managed by the operating system kernel, but completely controlled by the program (that is, executed in user mode).

The advantage of this is that the performance has been greatly improved, and it will not consume resources like thread switching.

The yield is the syntax in python. When the coroutine reaches the yield keyword, it will pause on that line. The coroutine will receive the data and continue execution until the main thread calls the send method to send the data.

However, yield makes the coroutine pause, which is essentially different from thread blocking. The suspension of the coroutine is completely controlled by the program, and the blocking state of the thread is switched by the operating system kernel.

Therefore, the overhead of the coroutine is much smaller than the overhead of the thread.

  1. Write the constructor, destructor and assignment function of class String.
    The prototype of the known class String is:

class String
{ public: String(const char *str = NULL); //Ordinary constructor String(const String &other); //Copy constructor ~ String(void); //Destructor String & operator =(const String &other); //Assignment function private: char *m_String; //Private member, save string };







#include
using namespace std;
class String
{ public: String(const char *str = NULL); //Ordinary constructor String(const String &other); //Copy constructor ~ String(void); //Destructor String & operator =(const String &other); //Assignment function private: char *m_String; //Private member, save string };







String::~String(void)
{ cout << “Destructing”<< endl; if (m_String != NULL) //If m_String is not NULL, release heap memory { delete [] m_String; m_String = NULL; //release NULL } }






String::String(const char *str)
{ cout << "Construcing" << endl; if(str == NULL) //If str is NULL, save the empty string "" { m_String = new char[1]; //Allocate a byte *m_String ='\0'; //Assign it to the end of the string } else { //Allocate space to accommodate the content of str m_String = new char[strlen(str) + 1]; //Copy str to private member strcpy(m_String, str); } }













String::String(const String &other)
{ cout << "Constructing Copy" << endl; //Allocate space to accommodate the content of str m_String = new char[strlen(other.m_String) + 1]; //Copy str to private members strcpy(m_String, other.m_String); }





String & String:perator = (const String &other)
{ cout << "Operate = Function" << endl; if(this == &other) //if the object and other are the same object { //return itself directly return *this ; } delete [] m_String; //Release heap memory m_String = new char[strlen(other.m_String)+1]; strcpy(m_String, other.m_String); return *this; }









int main()
{ String a("hello"); //Call the ordinary constructor String b("world"); //Call the ordinary constructor String c(a); //Call the copy constructor c = b; / /Call assignment function



return 0;
}
12. Several ways to
ensure thread safety ? There are several ways to ensure thread safety: competition and atomic operations, synchronization and locking, reentrant, over-optimization.

1. Competition and atomic operations

Multiple threads simultaneously access and modify a data, which may cause serious consequences. The reason for the serious consequences is that many operations are compiled by the operating system into more than one instruction after the assembly code, so during execution, it may be interrupted by the scheduling system to execute other code after half of the execution. Generally, the operation of a single instruction is called Atomic, because the execution of a single instruction will not be interrupted anyway.

Therefore, in order to avoid the occurrence of abnormalities in multi-threaded operation data, the Linux system provides atomic instructions for some common operations to ensure thread safety. However, they are only suitable for relatively simple occasions, and other methods should be used in complex situations.

2. Synchronization and lock

In order to avoid unpredictable consequences when multiple threads read and write a data at the same time, developers should synchronize the access of each thread to the same data, that is to say, when one thread accesses the data, other threads must not share One data is accessed.

The most common method of synchronization is to use a lock (Lock), which is a non-mandatory mechanism. Each thread first tries to acquire the lock before accessing data or resources, and releases the lock after the end of the access; when the lock is already occupied When trying to acquire a lock, the thread will wait until the lock is available again.

The binary semaphore is the simplest kind of lock. It has only two states: occupied and non-occupied. It is suitable for resources that can only be accessed exclusively by a single thread. For resources that allow concurrent access by multiple threads, multiple semaphores (referred to as semaphores) are used.

3. Reentrant

A function is re-entered, which means that the function has not been executed, but due to external or internal factors, the function is executed again. A function is called reentrant, indicating that the function will not produce any undesirable consequences after being reentrant. Reentrancy is a strong guarantee for concurrency safety. A reentrant function can be used with confidence in a multi-threaded environment.

4. Over-optimization

In many cases, even if we use locks reasonably, thread safety may not be guaranteed. Therefore, we may over-optimize the code to ensure thread safety.

We can use the volatile keyword to try to prevent over-optimization. It can do two things: first, to prevent the compiler from caching a variable to a register without writing it back in order to increase speed; second, to prevent the compiler from adjusting the instructions for operating volatile variables order.

In another case, the out-of-order execution of the CPU makes it difficult to ensure the safety of multithreading. The usual solution is to call an instruction provided by the CPU, which is often called barrier, which prevents the CPU from before the instruction. After the instruction is exchanged to the barrier, and vice versa.

  1. Why does the process switch happen? How to handle process switching?
    Reason: interrupt occurs; higher priority process wakes up; process runs out of time slice; resource is blocked;

step:

(1) Save the context of the processor

(2) Update the PCB of the running process with the new status and other relevant information

(3) Move the original process to the appropriate queue [ready, blocked]

Select another process to be executed, update the PCB of the selected process, and load it into the CPU

  1. Ways of congestion control? How do you do it? What's the timing of fast retransmission?
    (1) Start slowly

(2) Congestion avoidance

(3) Fast retransmission [3 out-of-sequence packet confirmation received]

(4) Quick recovery

  1. How to judge whether two structures are equal?
    Determine whether two structures are equal: overloaded operator "=="

The memcpy function cannot be used to determine whether two structures are equal: the memcmp function compares byte by byte, while the struct has byte alignment. When the byte is aligned, the content of the bytes added is random, which will generate garbage values, so it cannot Compare.

#include
using namespace std;

struct s
{
int a;
int b;
bool operator == (const s &rhs);
};

bool s::operator == (const s &rhs)
{
return ((a == rhs.a) && (b == rhs.b));
}

int main()
{ struct s s1, s2; s1.a = 1; s1.b = 2; s2.a = 1; s2.b = 2; if (s1 == s2) cout << "Two structures "Equal" << endl; else cout << "The two structures are not equal" << endl; return 0; } The space is limited. Let me share it here today. If you need more interview questions and learning materials, you can add Q skirt 812855908, Free share












Insert picture description here
Insert picture description here

Guess you like

Origin blog.csdn.net/qq_40989769/article/details/105053431