嵌入式 C语言常见面试试题集锦

1、volatile 关键字 

volatile 总是与优化有关,编译器有一种技术叫做数据流分析,分析程序中的变量在哪里赋值、在哪里使用、在哪里失效,分析结果可以用于常量合并,常量传播等优化,进一步可以死代码消除。一般来 说,volatile 关键字只用在以下三种情况: 

a) 中断服务函数中修改的供其它程序检测的变量需要加volatile(参考本书高级实验程序) 

b) 多任务环境下各任务间共享的标志应该加volatile 

c) 存储器映射的硬件寄存器通常也要加volatile 说明,因为每次对它的读写都可能由不同意义 

总之,volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素 更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码 就不再进行优化,从而可以提供对特殊地址的稳定访问。

2、宏使用

#define MAX(A,B)   {(A)>(B)?(A):(B)} 

注意:在调用时一定要注意这个宏定义的副作用,如下调用:

((++*p)<=(x)?(++*p):(x)

p 指针就自加了两次,违背了 MIN 的本意。

3、sizeof 和 strlen 的区别

sizeof 和 strlen 有以下区别:

1 sizeof 是一个操作符,strlen 是库函数。

2 sizeof 的参数可以是数据的类型,也可以是变量,而 strlen 只能以结尾为‘\0‘的字符串作参数。

3 编译器在编译时就计算出了 sizeof 的结果。而 strlen 函数必须在运行时才能计算出来。并且 sizeof 计算的是数据类型占内存的大小,而 strlen 计算的是字符串实际的长度。

4 数组做 sizeof 的参数不退化,传递给 strlen 就退化为指针了。

注意:有些是操作符看起来像是函数,而有些函数名看起来又像操作符,这类容易混淆的名称一定要加以区分,否则遇到数组名这类特殊数据类型作参数时就很容易出错。最容易混淆为函数的操作符就是 sizeof。

4、设置地址为 0x67a9 的整型变量的值为 0xaa66

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

或 *((int*)0x67a9) =  0xaa66;

5、typedef 和 define 有什么区别

(1) 用法不同:typedef 用来定义一种数据类型的别名,增强程序的可读性。define 主要用来定义常量,以及书写复杂使用频繁的宏。

(2) 执行时间不同:typedef 是编译过程的一部分,有类型检查的功能。define 是宏定义,是预编译的部分,其发生在编译之前,只是简单的进行字符串的替换,不进行类型的检查。

(3) 作用域不同:typedef 有作用域限定。define 不受作用域约束,只要是在 define 声明后的引用都是正确的。

(4) 对指针的操作不同:typedef 和 define 定义的指针时有很大的区别。

注意:typedef 定义是语句,因为句尾要加上分号。而 define 不是语句,千万不能在句尾加分号。

6、关键字 const 是什么

const 用来定义一个只读的变量或对象。主要优点:便于类型检查、同宏定义一样可以方便地进行参数的修改和调整、节省空间,避免不必要的内存分配、可为函数重载提供参考。

说明:const 修饰函数参数,是一种编程规范的要求,便于阅读,一看即知这个参数不能被改变,实现时不易出错。 const修饰成员函数不可修改成员变量。

7、static 有什么作用

static 在 C 中主要用于定义全局静态变量、定义局部静态变量、定义静态函数。在 C++中新增了两种作用:定义静态数据成员、静态函数成员。

注意:因为 static 定义的变量分配在静态区,所以其定义的变量的默认值为 0,普通变量的默认值为随机数,在定义指针变量时要特别注意。

8、extern 有什么作用

extern 标识的变量或者函数声明其定义在别的文件中,提示编译器遇到此变量和函数时在其它模块中寻找其定义。

9、数组名和指针的区别

请写出以下代码的打印结果:

#include <iostream.h>

#include <string.h>

void main(void)

{

char str[13]="Hello world!";

char *pStr="Hello world!";

cout<<sizeof(str)<<endl;

cout<<sizeof(pStr)<<endl;

cout<<strlen(str)<<endl;

cout<<strlen(pStr)<<endl;

return;

}

【答案】

打印结果:

13

4

12 12

注意:一定要记得数组名并不是真正意义上的指针,它的内涵要比指针丰富的多。但是当数组名当做参数传递给函数后,其失去原来的含义,变作普通的指针。另外要注意 sizeof 不是函数,只是操作符。

10、简述 strcpy、sprintf 与 memcpy 的区别

三者主要有以下不同之处:

(1) 操作对象不同,strcpy 的两个操作对象均为字符串,sprintf 的操作源对象可以是多种数据类型,目的操作对象是字符串,memcpy 的两个对象就是两个任意可操作的内存地址,并不限于何种数据类型。

(2) 执行效率不同,memcpy 最高,strcpy 次之,sprintf 的效率最低。

(3) 实现功能不同,strcpy 主要实现字符串变量间的拷贝,sprintf 主要实现其他数据类型格式到字符串的转化,memcpy 主要是内存块间的拷贝。

说明:strcpy、sprintf 与 memcpy 都可以实现拷贝的功能,但是针对的对象不同,根据实际需求,来选择合适的函数实现拷贝功能。

11、编码实现某一变量某位清 0 或置 1

给定一个整型变量 a,写两段代码,第一个设置 a 的 bit 3,第二个清 a 的 bit 3,在以上两个操作中,要保持其他位不变。

【答案】

#define BIT3 (0x1 << 3 )

Satic int a;         

//设置 a 的 bit 3:         

void set_bit3( void )

{

         a |= BIT3;                                     

}         //将 a 第 3 位置 1

//清 a 的 bit 3         

void set_bit3( void )

{

         a &= ~BIT3;                                     

}         //将 a 第 3 位清零

说明:在置或清变量或寄存器的某一位时,一定要注意不要影响其他位。所以用加减法是很难实现的。

12、评论下面这个中断函数

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

__interrupt double compute_area (double radius) 

double area = PI * radius * radius;  printf(" Area = %f", area);  return area; 

}

【答案】

这段中断服务程序主要有以下四个问题:

(1) ISR 不能返回一个值。

(2) ISR 不能传递参数。

(3) 在 ISR 中做浮点运算是不明智的。

(4) printf()经常有重入和性能上的问题。

注意:本题的第三个和第四个问题虽不是考察的重点,但是如果能提到这两点可给面试官留下一个好印象。

13、考察const符号常量

比较const char *p、char const *p、char * const p说明三者区别

第一个:p是一个指向const char的指针,p是可以改变指向的,但是p指向的值是不能改变的。

第二个:p指向的恰好是一个指向const的char的普通指针。

第三个:p是一个指针,这个指针是指向char的const指针。

第一个和第二个的定义是一样的。

14、在不用第三方参数的情况下,交换两个参数的值

a = a + b; 

b = a – b;

a = a – b;

15、识别函数与指针

void * ( * (*fp1)(int))[10];

float (*(* fp2)(int,int,int))(int);

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

分别表示什么意思?

答:

void * ( * (*fp1)(int))[10];   fp1是一个指针,指向一个函数,这个函数的参数是int型,函数的返回值是一个指针,这个指针指向一个数组。这个数组有十个元素,每个元素都是一个void*型指针。

float (*(* fp2)(int,int,int))(int);  fp2是一个指针,指向一个函数,这个函数的参数为3个int型,函数的返回值是一个指针,这个指针指向一个函数。这个函数的参数为int型,函数的返回值是float型。

int (* ( * fp3)())[10]();   fp3是一个指针,指向一个函数,这个函数的参数为空,函数的返回值是一个指针,这个指针指向一个数组,这个数组有十个元素,每个元素是一个指针,指向一个函数,这个函数的参数为空,函数的返回值是int型。

16、内存的分配方式有几种?

a.从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量。

b.在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

c.从堆上分配,亦称为动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。

17、全局变量和局部变量有什么区别,是怎么实现的。操作系统和编译器是怎么知道的

答:

a.生命周期不同

全局变量随主程序创建而创建,随主程序销毁而销毁。

局部变量在局部函数内部,甚至局部循环体等内部存在,退出就不存在,内存中分配在全局数据区。

b.使用方式不同

通过声明后全局变量程序的各个部分都可以用到,局部变量只能在局部使用,分配在栈区。

操作系统和编译器通过内存分配的位置来知道的,全局变量分配在全局数据段并且在程序开始运行的时候被加载。局部变量则分配在堆栈里面。

18.一个栈的入栈序列是 A,B,C,D,E,则栈的不可能的输出序列是

A、EDCBA;

B、DECBA;   

C、DCEAB;

D、ABCDE 

19、写出判断ABCD四个表达式的是否正确, 若正确, 写出经过表达式中 a 的值

int a = 4; 

(A)、a += (a++);

(B)、a += (++a) ; 

(C)、(a++) += a;

(D)、(++a) += (a++); 

a = ?

答:

C错误,左侧不是一个有效变量,不能赋值,可改为 (++a) += a; 改后答案依次为 9,10,10,11

20、输入一个字符串,将其逆序输出。

void main()
{

        char a[50];
        memset(a, 0, sizeof(a));
        int i = 0, j;
        char t;
        cin.getline(a, 50, '\n');
        for (i = 0, j = strlen(a) - 1; i < strlen(a) / 2; i++, j--)
        {

                t = a[i];
                a[i] = a[j];
                a[j] = t;

        }
        cout << a << endl;

}

21、TCP和UDP的区别

TCP是面向连接的协议,提供的是可靠传输,在收发数据前需要通过三次握手建立连接,使用ACK对收发的数据进行正确性检验。而UDP是无连接的协议,不管对方有没有收到或者收到的数据是否正确。

TCP提供流量控制和拥塞控制,而UDP没有。

TCP对系统资源的要求高于UDP,所以速度也比UDP慢。

TCP数据包是没有边界的,会出现粘包的问题,UDP包是独立的,不会出现粘包问题。

所以在应用方面,如果强调数据的完整性和正确性用TCP,当要求性能和速度的时候,使用UDP更加合适。

注:单凭TCP是不能保证完整性的,要是有黑客伪造TCP包,是无法识别的。

22、请解释(*(void (*)())0)()的含义?

  • void (*0)():是一个返回值为void,参数为空的函数指针0;
  • (void (*)())0:把0转变成一个返回值为void,参数为空的函数指针;
  • *(void (*)())0:在上句的基础上加*表示整个是一个返回值为void,无参数,并且起始地址为0的函数的名字;
  • (*(void (*)())0)():上句的函数名所对应的函数的调用。

 

23、请编程实现字符串转换为数字?

编码实现函数atoi(),设计一个程序,把一个字符串转化为一个整型数值,例如:字符串"5486321",转化成整型5486321。

int myatoi(const char *str)
{

    int num = 0;  //保存转换后的数值
    int isNegative = 0;  //记录字符串中是否有负号
    int n = 0;
    char *p = str;
    if (p == NULL)  //判断指针的合法性
        return -1;
   
    while (*p++ != '\0')  //计算字符串长度
        n++;
   
    p = str;
    if (p[0] == '-')  //判断数组是否有负号
        isNegative = 1;

    for (int i = 0; i < n; i++)
    {

        char temp = *p++;
        if (temp > '9' || temp < '0')  //滤除非数字字符
            continue;
      
        if (num != 0 || temp != '0')  //滤除字符串开始的'0'字符
        {

            temp -= 0x30;   //将数字字转换为数值
            num += temp * int(pow(10, n - 1 - i));
        }
    }
    if (isNegative)
         return (0 - num);
    else
        return num;

}

24、TCP(UDP,IP)等首部的认识(http请求报文构成)

TCP的头部大致包括:源端口,目的端口,序号,确认号,偏移位,标志位,校验和等等

UDP的头部则包括:源端口,目的端口,长度,校验和。

IP数据包的头部包括:源IP地址,目的IP地址,协议,校验和,总长度等等

25、TCP的三次握手与四次挥手的详细介绍(TCP连接建立与断开是热门问题)

三次握手

第一次握手:首先client给server发送连接请求报文,在这个报文中,包含了SYN=1,client_seq=任意值i,发送之后处于SYN-SENT状态,这是第一次握手

第二次握手:server端接收到了这个请求,并分配资源,同时给client返回一个ACK报文,这个报文中呢包含了这些字段,标志位SYN和ACK都为1,而小ack为i+1,此时位于SYN-RCVD状态,这是第二次握手

第三次握手:client收到server发来的ACK信息后呢,他会看到server发过来的小ack是i+1,这时他知道了server收到了消息,也给server回一个ACK报文,报文中同样包含了ACK=1这样的消息,同时呢,还包括了client_ack=k+1这样的字段,这样呢三次握手之后,连接就建立了,client进入established(已建立连接)状态

四次挥手断开连接:

TCP断开连接通常是由一方主动,一方被动的,这里我们假设client主动,server被动

第一次挥手:当client没有数据要发送给server了,他会给server发送一个FIN报文,告诉server:“我已经没有数据要发给你了,但是你要是还想给我发数据的话,你就接着发,但是你得告诉我你收到我的关闭信息了”,这是第一次挥手,挥手之后client进入FIN_WAIT_1的第一阶段

第二次挥手:当server收到client发来的FIN报文后,告诉client:“我收到你的FIN消息了,但是你等我发完的”此时给client返回一个ACK信息,并且呢ack=seq+1,这是第二次挥手,挥手之后呢server进入CLOSE_WAIT阶段,而client收到之后处于FIN_WAIT_2第二阶段

第三次挥手:当server发完所有数据时,他会给client发送一个FIN报文,告诉client说“我传完数据了,现在要关闭连接了”,然后呢server变成LAST_ACK状态,等着client最后的ACK信息,这是第三次挥手

第四次挥手:当client收到这个FIN报文时,他会对这个消息进行确认,即给server发ACK信息,但是它不相信网络,怕server收不到信息,它会进入TIME_WAIT状态,万一server没收到ACK消息它可以可以重传,而当server收到这个ACK信息后,就正式关闭了tcp连接,处于CLOSED状态,而client等待了2MSL这样长时间后还没等到消息,它知道server已经关闭连接了,于是乎他自己也断开了,这是第四次挥手,这样tcp连接就断开了。

26、  指针的几种典型应用情况?

答:

int *p[n];—–指针数组,每个元素均为指向整型数据的指针。

int (*)p[n];—p为指向一维数组的指针,这个一维数组有n个整型数据。

int *p();——函数带回指针,指针指向返回的值。

int (*)p();—-p为指向函数的指针。

27、如何把字符串变成数字?

atoi

atof

使用c语言里面的内置函数可以做到。你也可以自己写函数。

28、什么是野指针

野指针不是NULL指针,是未初始化或者未清零的指针,它指向的内存地址不是程序员所期望的,可能指向了受限的内存。 成因:

指针变量没有被初始化;

指针指向的内存被释放了,但是指针没有置NULL;

指针超过了变量了的作用范围,比如b[10],指针b+11;

29、栈溢出的原因以及解决方法

栈溢出是指函数中的局部变量造成的溢出(注:函数中形参和函数中的局部变量存放在栈上) 栈的大小通常是1M-2M,所以栈溢出包含两种情况,一是分配的的大小超过栈的最大值,二是分配的大小没有超过最大值,但是接收的buf比原buf小。

函数调用层次过深,每调用一次,函数的参数、局部变量等信息就压一次栈;

局部变量体积太大;

解决办法大致说来也有两种:

增加栈内存的数目;如果是不超过栈大小但是分配值小的,就增大分配的大小;

使用堆内存;具体实现由很多种方法可以直接把数组定义改成指针,然后动态申请内存;也可以把局部变量变成全局变量,一个偷懒的办法是直接在定义前边加个static,呵呵,直接变成静态变量(实质就是全局变量);

 

30、指针数组和数组指针的区别

  • 数组指针,是指向数组的指针,而指针数组则是指该数组的元素均为指针。
  • 数组指针,是指向数组的指针,其本质为指针,形式如下。如 int (*p)[n],p即为指向数组的指针,()优先级高,首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长。也就是说执行p+1时,p要跨过n个整型数据的长度。数组指针是指向数组首元素的地址的指针,其本质为指针,可以看成是二级指针。

类型名 (*数组标识符)[数组长度]  int (*p)[n];

 

  • 指针数组,在C语言和C++中,数组元素全为指针的数组称为指针数组,其中一维指针数组的定义形式如下。指针数组中每一个元素均为指针,其本质为数组。如 int p[n], []优先级高,先与p结合成为一个数组,再由int说明这是一个整型指针数组,它有n个指针类型的数组元素。这里执行p+1时,则p指向下一个数组元素,这样赋值是错误的:p=a;因为p是个不可知的表示,只存在p[0]、p[1]、p[2]…p[n-1],而且它们分别是指针变量可以用来存放变量地址。但可以这样 p=a; 这里p表示指针数组第一个元素的值,a的首地址的值。

类型名 *数组标识符[数组长度]  int *p[n];

 

31、写出float x 零值的比较语句

If(x>0.000001 && x<-0.000001)

 

32用变量a给出下面的定义

  • a) 一个整型数(An integer)
  • b)一个指向整型数的指针( A pointer to an integer)
  • c)一个指向指针的的指针,它指向的指针是指向一个整型数( A pointer to a pointer to an intege)
  • d)一个有10个整型数的数组( An array of 10 integers)
  • e) 一个有10个指针的数组,该指针是指向一个整型数的。(An array of 10 pointers to integers)
  • f) 一个指向有10个整型数数组的指针( A pointer to an array of 10 integers)
  • g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a function that takes an integer as an argument and returns an integer)
  • h) 一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数 ( An array of ten pointers to functions that take an integer argument and return an integer )

a)int a;

b)int *a;

c)int **a;

d)int a[10]

e)int *a[10]

f)int (*a)[10]

g)int (*a)(int a)

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

33、下面的代码输出是什么,为什么?

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

考察点:

这 个问题测试你是否懂得C语言中的整数自动转换原则,我发现有些开发者懂得极少这些东西。

不管如何,这无符号整型问题的答案是输出是 ">6"。原因 是当表达式中存在有符号类型和无符号类型时所有的操作数都自动转换为无符号类型。

因此-20变成了一个非常大的正整数,所以该表达式计算出的结果大于6。这一点对于应当频繁用到无符号数据类型的嵌入式系统来说是丰常重要的。

34、C语言大小端判断函数

1、大端模式:**低地址**存**高字节**
地址:0x00     0x01     0x02     0x03
数据:0x12     0x34     0x56     0x78
2、小端模式:**低地址**存**低字节**
地址:0x00        0x01    0x02    0x03
数据:0x78        0x56    0x34    0x12

方法一:

#include <stdio.h>

int main(void)
{
	int a = 0x12345678;
	int *pa = &a;
	char *p = (char*)(&a);
	cahr b = *pa;
	
	printf("%x",*p);//如果结果是12就是大端,否则就是小端
	//printf("%s",((*p)==0x12)?("大端"):("小端"));
	//printf("%s",((char)a == 0x12)?"大端":"小端");
	//printf("%s",(b == 0x12)?"大端":"小端");
	/*上面四个printf都可以测试*/
	
	return 0;
}

方法二:

#include <stdio.h>

int main(void)
{
	int a = 0x12345678;
	int *pa = &a;
	char *p = (char*)(&a);
	cahr b = *pa;
	
	printf("%x",*p);//如果结果是12就是大端,否则就是小端
	//printf("%s",((*p)==0x12)?("大端"):("小端"));
	//printf("%s",((char)a == 0x12)?"大端":"小端");
	//printf("%s",(b == 0x12)?"大端":"小端");
	/*上面四个printf都可以测试*/
	
	return 0;
}

  • 3
    点赞
  • 74
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值