嵌入式软件工程师经典笔试题

1、用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)

解答:这一题主要容易错的地方就是:意识到这个表达式将使一个16位机的整型数溢出,因此要用到长整型符号L,告诉编译器这个常数是的长整型数。

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

2、写一个"标准"宏MIN,这个宏输入两个参数并返回较小的一个。

解答:这一题主要容易错的地方就是:懂得在宏中小心地把参数用括号括起来。

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

当然,使用宏也是有副作用的。就拿这一个例子来说:该宏定义对MIN(*p++, b)的作用结果是:((*p++) <= (b) ? (*p++) : (b)) 这个表达式会产生副作用,指针p会作两次++自增操作。

3、用变量a给出下面的定义:一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数。

解答:这一道题主要容易错的地方就是:函数指针、指针数组。

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

4、关键字static的作用是什么?

解答:在C语言中,关键字static有三个明显的作用:

在函数体中,一个被声明为静态的变量在这一函数被调用过程中只会被分配一次内存,且整个运行期间不会重新分配;
在函数体外、某个源文件内,一个被声明为静态的变量只可被该源文件内的所有函数访问,但不能被其他源文件的函数访问。它是一个本地的全局变量;
在某个源文件内,一个被声明为静态的函数仅仅只可以被这个源文件的其它函数调用。也就是说,这个函数被限制在声明它的源文件的本地范围之内使用。
5、关键字const的作用是什么?

解答:简单地说,const意味着常数。

La variable définie par const, sa valeur ne peut pas être changée et reste fixe dans toute la portée ;
comme la définition de la macro, elle peut éviter l'apparition de nombres ambigus, et il est également très pratique d'ajuster et de modifier les paramètres ;
elle peut protéger les modifications Quelque chose pour empêcher toute modification accidentelle et améliorer la robustesse du programme. const est assuré par le compilateur effectuant des vérifications au moment de la compilation.
const et pointeurs

Que signifient les déclarations suivantes :`

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;`

Les fonctions des deux premiers sont les mêmes, a est un entier constant ;

Le troisième signifie que a est un pointeur vers un entier constant (c'est-à-dire que les entiers sont immuables, mais les pointeurs peuvent l'être) ; le quatrième
signifie que a est un pointeur constant vers un entier (c'est-à-dire , l'entier pointé par le pointeur est modifiable, mais le pointeur n'est pas modifiable); les
deux derniers signifient que a est un pointeur constant sur un entier constant (c'est-à-dire que l'entier pointé par le pointeur n'est pas modifiable et que le pointeur est également immuable).
const et fonctions

const est généralement utilisé dans les paramètres de fonction. Si le paramètre est un pointeur, afin d'empêcher que les données pointées par le pointeur ne soient modifiées à l'intérieur de la fonction, const peut être utilisé pour le restreindre. Par exemple, il existe de nombreux paramètres const modifiés dans le programme String :

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

const peut également signifier que la fonction renvoie une constante, qui est placée dans la valeur de retour de la fonction. Par exemple:

const char * GetString(void);

Dans la déclaration et la définition d'une fonction membre de classe, const est placé après la table des paramètres de la fonction et avant le corps de la fonction, indiquant que le pointeur this de la fonction est une constante et que les données membres de l'objet ne peuvent pas être modifiées. Par exemple:

void getId() const;

6. Quelle est la signification du mot-clé volatile ? Et trois exemples différents sont donnés.

Réponse : Une variable définie comme volatile signifie que la variable peut être modifiée de manière inattendue, de sorte que le compilateur n'assumera pas la valeur de la variable. Pour être précis, l'optimiseur doit relire attentivement la valeur de cette variable à chaque fois qu'il l'utilise, au lieu d'utiliser une sauvegarde stockée dans un registre. Voici quelques exemples de variables volatiles :

存储器映射的硬件寄存器通常也要加voliate,因为每次对它的读写都可能有不同意义;
在中断函数中的交互变量,一定要加上volatile关键字修饰,这样每次读取非自动存储类型的值(全局变量,静态变量)都是在其内存地址中读取的,确保是我们想要的数据;
多任务环境下各任务间共享的标志应该加volatile。
一个参数既可以是const还可以是volatile吗?

可以的,例如只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。软件不能改变,并不意味着我硬件不能改变你的值,这就是单片机中的应用。

参考文章:C语言中的volatile——让我保持原样。

一个指针可以是volatile 吗?

可以。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。

下面的函数有什么错误:`

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

这段代码的目的是用来返指针ptr指向值的平方,但是,由于ptr指向一个volatile型参数,编译器将产生类似下面的代码:

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

由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:

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

7、给定一个整型变量a,写两段代码,第一个设置a的bit3,第二个清除a的bit3。

解答:这道题清除a的bit3,使用“&=~”的方法。

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

8、嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。在某工程中,要求设置一绝对地址为0x67a9的整型变量的值为0xaa66。

解答:这一问题测试你是否知道为了访问一绝对地址,把一个整型数(绝对地址)强制转换为一指针是合法的。

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

9、中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展——让标准C支持中断。具代表事实是,产生了一个新的关键字 __interrupt。下面的代码就使用了__interrupt关键字去定义了一个中断服务子程序(ISR),请评论一下这段代码的。

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

解答:这个函数有太多的错误了,以至让人不知从何说起了:

ISR 不能传递参数,不能返回一个值;
在许多的处理器/编译器中,浮点一般都是不可重入的。有些处理器/编译器需要让额处的寄存器入栈,有些处理器/编译器就是不允许在ISR中做浮点运算。此外,ISR应该是短而有效率的,在ISR中做浮点运算是不明智的;
printf(char * lpFormatString,…)函数会带来重入和性能问题,不能在ISR中采用。
关于这些要求的解释:

a.为什么不能有返回值?

中断服务函数的调用是硬件级别的,当中断产生,pc指针强制跳转到对应的中断服务函数入口,进入中断具有随机性,并不是某段代码对其进行调用,那么如果有返回值它的返回值返回给谁?显然这个返回值毫无意义,如果有返回值,它必定需要进行压栈操作,这样一来何时出栈怎么出栈将变得无法解决。

b.不能向ISR传递参数?

同理,也是由于这样会破坏栈的原因,因为函数传递参数必定会要求压栈出栈操作,由于进入中断服务函数的随机行,谁给它传递参数都成问题。

c.ISR应尽可能的短小精悍?

如果某个中断频繁产生,而它对应的ISR相当的耗时,那么对中断的响应就会无限的延迟,会丢掉很多的中断请求。

d.printf(char * lpFormatString,…)函数会带来重入和性能问题,不能在ISR中采用。

这就涉及到一个中断嵌套问题,由于printf之类的glibc函数采用的是缓冲机制,这个缓冲区是共享的,相当于一个全局变量,第一层中断来时,它向缓冲里面写入一些部分内容,恰好这时来了个优先级更高的中断,它同样调用了printf,也向缓冲里面写入一些内容,这样缓冲区的内容就错乱了。

Les fonctions réentrantes sont principalement utilisées dans les environnements multitâches. Une fonction réentrante est simplement une fonction qui peut être interrompue, c'est-à-dire qu'elle peut être interrompue à tout moment pendant l'exécution de cette fonction, et transférée à l'ordonnancement de l'OS pour exécution. Un morceau de code , et il n'y aura pas d'erreur lors du retour du contrôle ; les fonctions non réentrantes utilisent certaines ressources système, telles que la zone de variable globale, la table des vecteurs d'interruption, etc., donc si elle est interrompue, des problèmes peuvent survenir. Les fonctions ne peuvent pas être exécutées dans un environnement multitâche environnement.

La connexion et la différence entre la fonction de service d'interruption et l'appel de fonction :

Connexion : les deux doivent protéger le point d'arrêt (c'est-à-dire l'adresse de l'instruction suivante), sauter au sous-programme ou interrompre le programme de service, protéger le contexte, le sous-programme ou la gestion des interruptions, restaurer le contexte, restaurer le point d'arrêt (c'est-à-dire retourner au programme principal). Les deux peuvent être imbriqués, c'est-à-dire que le sous-programme en cours d'exécution est transféré vers un autre sous-programme ou que le programme d'interruption en cours de traitement est interrompu par une autre nouvelle demande d'interruption, et l'imbrication peut être à plusieurs niveaux.

Différence : la différence fondamentale entre les deux se reflète principalement dans la différence de temps de service et d'objets de service. Premièrement, l'heure d'appel du processus de sous-programme est connue et fixe, c'est-à-dire que lorsque la commande d'appel (CALL) dans le programme principal est exécutée, le programme principal appelle le sous-programme et l'emplacement de la commande d'appel est connu et fixe. Le moment où le processus d'interruption se produit est généralement aléatoire.Lorsque la CPU reçoit une application d'interruption de la source d'interruption lors de l'exécution d'un certain programme principal, le processus d'interruption se produit et l'application d'interruption est généralement générée par le circuit matériel, et le temps d'application est aléatoire (l'heure d'occurrence de l'interruption logicielle est fixe), on peut également dire que l'appel du sous-programme est organisé à l'avance par le concepteur du programme et que l'exécution du programme de service d'interruption est déterminée de manière aléatoire par l'environnement de travail du système ;

Deuxièmement, le sous-programme sert complètement le programme principal, et les deux appartiennent à la relation maître-esclave.Lorsque le programme principal a besoin du sous-programme, il appelle le sous-programme et ramène le résultat de l'appel au programme principal pour continuer l'exécution. Le programme de service d'interruption et le programme principal ne sont généralement pas pertinents, et il n'est pas question de savoir qui sert qui, et les deux sont parallèles ;

Troisièmement, le processus d'appel de sous-programmes par le programme principal est entièrement un processus de traitement logiciel et ne nécessite pas de circuits matériels spéciaux, tandis que le système de traitement des interruptions est une combinaison de logiciel et de matériel, ce qui nécessite des circuits matériels spéciaux pour terminer le processus de traitement des interruptions.

Quatrièmement, plusieurs niveaux d'imbrication de sous-programmes peuvent être réalisés, et le nombre maximum de niveaux d'imbrication est limité par la taille de la pile ouverte par la mémoire de l'ordinateur, tandis que le nombre de niveaux d'imbrication d'interruption est principalement déterminé par le nombre de priorités d'interruption, et généralement le le nombre de niveaux de priorité ne sera pas très grand .

10. Quelle est la sortie du code suivant et pourquoi ?

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

Solution : Cette question teste si vous comprenez les principes de la conversion automatique d'entiers en langage C. J'ai constaté que certains développeurs comprennent très peu ces choses. Quoi qu'il en soit, la réponse au problème des entiers non signés est que la sortie est "> 6". La raison en est que tous les opérandes sont automatiquement convertis en types non signés lorsqu'il existe des types signés et non signés dans l'expression. Donc -20 devient un entier positif très grand, donc l'expression est évaluée à plus de 6.

Il y a un autre point de connaissance important :

Dans des circonstances normales, lors de l'exécution d'opérations sur des valeurs de type int, la vitesse de fonctionnement du processeur est la plus rapide. Sur x86, l'arithmétique 32 bits est deux fois plus rapide que l'arithmétique 16 bits. Le langage C est un langage qui met l'accent sur l'efficacité, il effectuera donc une promotion entière pour que le programme s'exécute le plus rapidement possible. Par conséquent, vous devez vous souvenir des règles de promotion d'entiers pour éviter certains problèmes de dépassement d'entiers.

La promotion d'entiers est une règle dans le langage de programmation C. Lors du calcul d'expressions (y compris les opérations de comparaison, les opérations arithmétiques, les opérations d'affectation, etc.), les types plus petits que le type int (char, char signé, char non signé, court, court non signé, etc. .) doit d'abord être promu au type int, puis effectuer l'opération de l'expression.

Quant à la méthode de promotion, elle consiste à effectuer une extension de bit en fonction du type d'origine (si le type d'origine est un caractère non signé, effectuez une extension nulle, si le type d'origine est un caractère signé, effectuez une extension de bit de signe) à 32 bits. C'est-à-dire:

Pour un caractère non signé, effectuez une extension de zéro, c'est-à-dire remplissez les bits de poids fort à gauche avec 0 à 32 bits ;
pour un caractère signé, effectuez une extension de bit de signe. Si son bit de signe est 0, alors le bit haut gauche est rempli de 0 à 32 bits ; si son bit de signe est 1, le bit haut gauche est rempli de 1 à 32 bits.
11. Évaluez l'extrait de code suivant :

unsigned int compzero = 0xFFFF;

Solution : Pour un processeur dont le type int n'est pas 16 bits, le code ci-dessus est incorrect. doit s'écrire comme suit :

unsigned int compzero = ~0;

12. Bien qu'ils ne soient pas aussi courants que les ordinateurs non embarqués, les systèmes embarqués ont toujours le processus d'allocation dynamique de mémoire à partir du tas. Alors, quels sont les problèmes possibles d'allocation dynamique de mémoire dans un système embarqué ?

Réponse : L'allocation dynamique posera inévitablement des problèmes :

Fuites de mémoire : les fuites de mémoire sont généralement causées par les défauts de codage du programme lui-même. Il n'y a pas d'opérations libres et autres opérations similaires après la mémoire malloc commune. Le système mallocs à plusieurs reprises pendant le processus en cours d'exécution, consommant la mémoire système, provoquant le noyau OOM , et un certain processus doit demander de la mémoire. de la mise à mort et de la sortie.
Fragmentation de la mémoire : la fragmentation de la mémoire est un problème système, malloc répété et libre, et la mémoire après la libération ne peut pas être recyclée par le système immédiatement. En effet, l'algorithme d'allocation chargé d'allouer dynamiquement de la mémoire rend ces mémoires libres inutilisables.Ce problème survient car ces mémoires libres apparaissent à des endroits différents de manière petite et discontinue.
Quelle est la sortie de l'extrait de code ci-dessous et pourquoi ?

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

Le paramètre de la fonction malloc() peut être 0.

13. Typedef est fréquemment utilisé en langage C pour déclarer un synonyme d'un type de données existant. Vous pouvez également faire quelque chose de similaire avec un préprocesseur. Par exemple, considérez l'exemple suivant :

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

L'intention des deux cas ci-dessus est de définir dPS et tPS comme un pointeur vers la structure s. Quelle méthode est la meilleure?

Réponse : typedef est préférable. Considérez l'exemple suivant :

dPS p1,p2;
tPS p3,p4;

S'il s'agit de l'extension de la première définition :

struct s * p1, p2;

p1 est un pointeur et p2 est une structure. Évidemment, ce n'est pas la réponse que nous voulons.

Je suppose que tu aimes

Origine blog.csdn.net/qizhi321123/article/details/131474717
conseillé
Classement