Embedded software engineer classic written test questions

1. Use the preprocessing directive #define to declare a constant to indicate how many seconds there are in a year (ignoring the leap year problem)

Answer: The main error-prone part of this question is: realizing that this expression will overflow an integer number of a 16-bit machine, so the long integer symbol L is used to tell the compiler that this constant is a long integer number .

 #define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL

2. Write a "standard" macro MIN that takes two arguments and returns the smaller one.

Answer: The main error-prone part of this question is: know how to carefully enclose the parameters in parentheses in the macro.

#define MIN(A,B) ((A)<=(B)?(A):(B)) 

Of course, using macros also has side effects. Take this example: the effect of the macro definition on MIN(*p++, b) is: ((*p++) <= (b) ? (*p++) : (b)) This expression will produce side effects , the pointer p will perform two ++ self-increment operations.

3. Use the variable a to give the following definition: an array with 10 pointers, the pointer points to a function, the function has an integer parameter and returns an integer number.

Answer: The main mistakes in this question are: function pointers and pointer arrays.

int (*a[10])(int);

4. What is the function of the keyword static?

Answer: In the C language, the keyword static has three obvious functions:

In the function body, a variable declared as static will only be allocated memory once when the function is called, and will not be reallocated during the entire runtime; outside the
function body or in a source file, a variable declared as Static variables can only be accessed by all functions in the source file, but not by functions in other source files. It is a local global variable;
within a source file, a function declared static can only be called by other functions in the source file. That is, the function is restricted to the local scope of the source file in which it is declared.
5. What is the function of the keyword const?

Answer: Simply put, const means constant.

The variable defined by const, its value cannot be changed, and remains fixed throughout the scope;
like the macro definition, it can avoid the appearance of ambiguous numbers, and it is also very convenient to adjust and modify parameters;
it can protect the modified Something to prevent accidental modification and enhance the robustness of the program. const is ensured by the compiler performing checks at compile time.
const and pointers

What do the following statements mean:`

1.const int a;
2.int const a;
3.const int *a;
4.int * const a;
5.const int * const a;
6.int const * const a;`

The functions of the first two are the same, a is a constant integer;

The third means that a is a pointer to a constant integer (that is, integers are immutable, but pointers can be); the fourth
means that a is a constant pointer to an integer (that is, , the integer pointed to by the pointer is modifiable, but the pointer is not modifiable); the
last two mean that a is a constant pointer to a constant integer (that is, the integer pointed to by the pointer is not modifiable , and the pointer is also immutable).
const and functions

const is usually used in function parameters. If the parameter is a pointer, in order to prevent the data pointed to by the pointer from being modified inside the function, const can be used to restrict it. For example, there are many const modified parameters in the String program:

void StringCopy(char* strDestination, const char *strSource);

const can also mean that the function returns a constant, which is placed in the return value of the function. for example:

const char * GetString(void);

In the declaration and definition of a class member function, const is placed after the parameter table of the function and before the function body, indicating that the this pointer of the function is a constant, and the data members of the object cannot be modified. for example:

void getId() const;

6. What is the meaning of the keyword volatile? And three different examples are given.

Answer: A variable defined as volatile means that the variable may be changed unexpectedly, so that the compiler will not assume the value of the variable. To be precise, the optimizer must carefully re-read the value of this variable every time it uses it, instead of using a backup stored in a register. Here are a few examples of volatile variables:

Memory-mapped hardware registers are usually added with voliate, because each reading and writing to it may have different meanings;
interactive variables in the interrupt function must be modified with the volatile keyword, so that each read is not automatically stored The value of the type (global variable, static variable) is read from its memory address to ensure that it is the data we want;
the flag shared between tasks in a multi-tasking environment should be added to volatile.
Can a parameter be both const and volatile?

Yes, for example a read-only status register. It is volatile because it can be changed unexpectedly. It is const because programs should not attempt to modify it. The fact that the software cannot be changed does not mean that my hardware cannot change your value. This is the application in the single-chip microcomputer.

Reference article: volatile in C - let me keep it as it is.

Can a pointer be volatile?

Can. Although this is not very common. An example is when a service subroutine modifies a pointer to a buffer.

What is wrong with the following function:`

int square(volatile int *ptr)
{
    
    
        return *ptr * *ptr;
}

The purpose of this code is to return the square of the value pointed to by the pointer ptr. However, since ptr points to a volatile parameter, the compiler will generate code similar to the following:

`int square(volatile int *ptr) 
{
    
    
  	int a,b;
   	a = *ptr;
    b = *ptr;
    return a * b;
}`

Since the value of *ptr may be changed unexpectedly, a and b may be different. As a result, this code may not return the square value you expect! The correct code is as follows:

long square(volatile int *ptr) 
{
    
    
    int a;
    a = *ptr;
    return a * a;
}

7. Given an integer variable a, write two pieces of code, the first one sets bit3 of a, and the second one clears bit3 of a.

Answer: This question clears bit3 of a, using the "&=~" method.

#define BIT3 (0x1 << 3)
static int a;
 
void set_bit3(void) 
{
    
    
    a |= BIT3;
}
void clear_bit3(void) 
{
    
    
    a &= ~BIT3;
}

8. Embedded systems often have features that require the programmer to access a specific memory location. In a certain project, it is required to set the value of an integer variable whose absolute address is 0x67a9 to 0xaa66.

Solution: This question tests whether you know that it is legal to cast an integer (absolute address) to a pointer in order to access an absolute address.

int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa55;

9. Interrupts are an important part of embedded systems, which has led many compiler developers to provide an extension - allowing standard C to support interrupts. The representative fact is that a new keyword __interrupt was created. The following code uses the __interrupt keyword to define an interrupt service subroutine (ISR), please comment on this code.

__interrupt double compute_area (double radius) 
{
    
    
    double area = PI * radius * radius;
    printf("/nArea = %f", area);
    return area;
}

Answer: There are so many errors in this function that people don't know where to start:

ISRs cannot pass parameters, cannot return a value;
floating point is generally not reentrant on many processors/compilers. Some processors/compilers need to push registers at the front of the stack, and some processors/compilers just don't allow floating point operations in the ISR. In addition, ISR should be short and efficient, it is unwise to do floating-point operations in ISR;
printf(char * lpFormatString,…) function will bring reentrancy and performance problems, and cannot be used in ISR.
Explanation of these requirements:

a. Why can't there be a return value?

The call of the interrupt service function is at the hardware level. When an interrupt is generated, the pc pointer is forced to jump to the corresponding interrupt service function entry. Entering the interrupt is random, and it is not called by a certain piece of code. If there is a return value, its Who is the return value returned to? Obviously, this return value is meaningless. If there is a return value, it must be pushed to the stack, so when and how to pop the stack will become unsolvable.

b. Can't pass parameters to ISR?

In the same way, it is also the reason why the stack will be destroyed in this way, because the function passing parameters will definitely require a push and pop operation. Since entering the random line of the interrupt service function, it is a problem for anyone to pass parameters to it.

c. The ISR should be as short and concise as possible?

If an interrupt is generated frequently and its corresponding ISR is quite time-consuming, the response to the interrupt will be infinitely delayed, and many interrupt requests will be lost.

The d.printf(char * lpFormatString,...) function will bring reentrancy and performance problems, and cannot be used in ISR.

This involves an interrupt nesting problem. Since glibc functions such as printf use a buffer mechanism, this buffer is shared, which is equivalent to a global variable. When the first layer interrupt comes, it writes some Part of the content, just at this time, a higher priority interrupt came, it also called printf, and also wrote some content into the buffer, so the content of the buffer was messed up.

Reentrant functions are mainly used in multitasking environments. A reentrant function is simply a function that can be interrupted, that is, it can be interrupted at any time during the execution of this function, and transferred to OS scheduling for execution. A piece of code, and there will be no error when returning control; non-reentrant functions use some system resources, such as global variable area, interrupt vector table, etc., so if it is interrupted, problems may occur. Functions cannot be run in a multitasking environment.

The connection and difference between interrupt service function and function call:

Connection: Both need to protect breakpoint (ie next instruction address), jump to subroutine or interrupt service routine, protect context, subroutine or interrupt handling, restore context, restore breakpoint (ie return to main program). Both can be nested, that is, the subroutine being executed is transferred to another subroutine or the interrupt program being processed is interrupted by another new interrupt request, and the nesting can be multi-level.

Difference: The fundamental difference between the two is mainly reflected in the difference in service time and service objects. First, the time of calling the subroutine process is known and fixed, that is, when the call command (CALL) in the main program is executed, the main program calls the subroutine, and the location of the call command is known and fixed. The time when the interrupt process occurs is generally random. When the CPU receives an interrupt application from the interrupt source when executing a certain main program, the interrupt process occurs, and the interrupt application is generally generated by the hardware circuit, and the application time is random ( The occurrence time of the soft interrupt is fixed), it can also be said that calling the subroutine is arranged by the program designer in advance, and the execution of the interrupt service program is randomly determined by the system working environment;

Secondly, the subroutine completely serves the main program, and the two belong to the master-slave relationship. When the main program needs the subroutine, it calls the subroutine, and brings the call result back to the main program to continue execution. The interrupt service program and the main program are generally irrelevant, and there is no question of who serves whom, and the two are parallel;

Third, the process of calling subroutines by the main program is completely a software processing process and does not require special hardware circuits, while the interrupt processing system is a combination of software and hardware, which requires special hardware circuits to complete the interrupt processing process;

Fourth, several levels of subroutine nesting can be realized, and the maximum number of nesting levels is limited by the stack size opened by the computer memory, while the number of interrupt nesting levels is mainly determined by the number of interrupt priorities, and generally the number of priority levels will not be very large .

10. What is the output of the following code and why?

void foo(void)
{
    
    
    unsigned int a = 6;
    int b = -20;
    (a+b > 6) ? puts("> 6") : puts("<= 6");
}

Solution: This question tests whether you understand the principles of integer automatic conversion in C language. I have found that some developers understand very little of these things. Anyway, the answer to the unsigned integer problem is that the output is ">6". The reason is that all operands are automatically converted to unsigned types when there are signed and unsigned types in the expression. So -20 becomes a very large positive integer, so the expression evaluates to greater than 6.

There is another important knowledge point:

Under normal circumstances, when performing operations on int type values, the operation speed of the CPU is the fastest. On x86, 32-bit arithmetic is twice as fast as 16-bit arithmetic. The C language is a language that focuses on efficiency, so it will perform integer promotion to make the program run as fast as possible. Therefore, you must remember the integer promotion rules to avoid some integer overflow problems.

Integer promotion is a regulation in the C programming language. When calculating expressions (including comparison operations, arithmetic operations, assignment operations, etc.), types smaller than the int type (char, signed char, unsigned char, short, unsigned short, etc.) must first be promoted to int type, and then perform the operation of the expression.

As for the method of promotion, it is to perform bit extension according to the original type (if the original type is unsigned char, perform zero extension, if the original type is signed char, perform sign bit extension) to 32 bits. That is to say:

For unsigned char, perform zero extension, that is, fill the high bits on the left with 0 to 32 bits;
for signed char, perform sign bit extension. If its sign bit is 0, then the left high bit is filled with 0 to 32 bits; if its sign bit is 1, the left high bit is filled with 1 to 32 bits.
11. Evaluate the following code snippet:

unsigned int compzero = 0xFFFF;

Solution: For a processor whose int type is not 16 bits, the above code is incorrect. should be written as follows:

unsigned int compzero = ~0;

12. Although not as common as non-embedded computers, embedded systems still have the process of dynamically allocating memory from the heap. So what are the possible problems of dynamically allocating memory in an embedded system?

Answer: Dynamic allocation will inevitably cause problems:

Memory leaks: Memory leaks are usually caused by coding defects in the program itself. There is no free or other similar operations after the common malloc memory. The system repeatedly mallocs during the running process, eating up the system memory, causing the kernel OOM, and a process needs to apply for memory. of the kill and exit.
Memory fragmentation: memory fragmentation is a system problem, repeated malloc and free, and the memory after free cannot be recycled by the system immediately. This is because the allocation algorithm responsible for dynamically allocating memory makes these free memories unusable. This problem occurs because these free memories appear in different places in a small and discontinuous manner.
What is the output of the code snippet below and why?

char *ptr;
if ((ptr = (char *)malloc(0)) == NULL) 
    puts("Got a null pointer");
else
    puts("Got a valid pointer");

The parameter of function malloc() can be 0.

13. Typedef is frequently used in C language to declare a synonym for an existing data type. You can also do something similar with a preprocessor. For example, consider the following example:

#define dPS struct s *
typedef struct s * tPS;

The intent of both cases above is to define dPS and tPS as a pointer to structure s. Which method is better?

Answer: typedef is better. Consider the following example:

dPS p1,p2;
tPS p3,p4;

If it is the extension of the first define:

struct s * p1, p2;

p1 is a pointer and p2 is a structure. Obviously, not the answer we want.

Guess you like

Origin blog.csdn.net/qizhi321123/article/details/131474717