嵌入式面试1:C语言经典笔面试题70道

C语言经典笔面试题70道

1.头文件包含:

#include 和#include “filename.h” 有什么区别?

对于#include ,编译器从标准库路径开始搜索filename.h ;
对于#include “filename.h” ,编译器从用户的工作路
径开始搜索filename.h 。找不到再按照默认路径寻找。

2.头文件中的ifndef/define/endif 干什么用?

防止该头文件被重复引用。

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

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

4.typedef和define的区别?

typedef 在C语言中频繁用以声明一个已经存在的数据类型的同义字。也可以用预处理器做类似的事。
思考一下下面的例子:

#define dPS struct s*    //类型,常量替换
typedef struct s* tPS;   //类型替换

以上两种情况的意图都是要定义dPS 和 tPS 为结构体指针类型。哪种方法更好呢?为什么?
这是一个非常微妙的问题,任何人答对这个问题(正当的原因)是应当被恭喜的。答案是:typedef更好。思考下面的例子:

dPS p1,p2;
tPS p3,p4;

第一个扩展为:

struct s * p1, p2;

上面的代码定义p1为一个指向结构体的指针,p2为一个实际的结构体,这也许不是你想要的。第二个例子正确地定义了p3 和p4 两个结构体指针。

这也就是我们平常开发时不使用#define来定义新类型(或者同义类型)的原因。
注:
经常#define用来替换常量,typedef用来替换类型

5.用typedef简化函数的声明:

声明signal函数:

void (*signal(int sig,void (*func)(int)))(int);

使用typedef简化函数声明:

typedef void (*HANDLER)(int);
HANDLER signal(int,HANDLER);

typedef定义了一种类型HANDLER,其是函数指针,指向接受一个int型参数无返回值的函数。
typedef在定义回调函数指针类型的时候大有用途。

6.已知一个数组table,用一个宏定义,求出数据的元素个数

#define NTBL(table) (sizeof(table)/sizeof(table[0]))

7.写一个标准宏MIN,输入两个参数返回较小的一个:

#define MIN(a,b)((a)>=(b)?(b):(a))

8.什么是大小端?写一个简单的程序判断系统的大小端。

小端:低位字节数据存储在低地址
大端:高位字节数据存储在低地址

例如:int a=0x12345678;(假设a首地址为0x2000)
地址: 0x2000  0x2001  0x2002  0x2003 
值 :  0x12   0x34    0x56   0x78     
这就是大端格式。

基于以上特点,我们不难写出判断程序:

#include 
union{
    unsigned int a;
    char b;
}test;
int main()
{
    test.a = 0x01;  //00 00 00 01
    if(test.b)//判断低字节是否为1
        printf("little endian");
    else
        printf("little endian");
    return 0;
}

上面的程序巧妙的利用了联合体共用地址空间的特点,如果是大端,01会被存放在高地处,那么b就不会等于1。

9.用变量a定义:

一个整型数 int a;
一个指向整型数的指针 int *a;
一个指向指针的指针,它指向的指针指向一个整型数 int **a;
一个有10个整型数的数组 int a[10];
一个有10个指针的数组,该指针是指向一个整型数 int *a[10];
一个指向有10个整型数数组的指针 int (*a)[10];
一个指向函数的指针,该函数有一个整型数参数并返回一个整型数 int (*a)(int);
一个有10个指针的数组,该指针指向一个函数,该函数有一个整型数参数并返回一个整型 int (*a[10])(int);
声明一个函数,它接受一个int参数,返回值是一个函数指针,函数指针指向的函数接受一个参数int且返回值是void void (*a(int))(int);

10.以下为Linux下的32位C程序,请计算sizeof的值。

char str[] = “Hello” ;                               
char *p = str ;                                      
int n = 10;         
void Func ( char str[100])                           
{                                                                                                 
}                                                                              
void * p = malloc( 100 );                                                                                       

请计算
(1)sizeof (str ) = 字符串
(2)sizeof ( p ) =指针
(3)sizeof ( n ) =整型
(4)sizeof( str ) =指针
(5)sizeof ( p ) =指针
答案:
(1)6、(2)4 、(3 )4 (4)4 (5)4

11.sizeof在什么阶段执行?它是函数还是啥?

编译,单目运算符。

12.位操作

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

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

13.符号转换

int main(void)
{
  unsigned int a = 6;
  int b = -20;
  char c;
  (a+b>6)?(c=1):(c=0);
  return 0;
}

当表达式中存在有符号类型和无符号类型时所有的操作数都自动转换为无符号类型。因此-20变成了一个非常大的正整数,所以该表达式计算出的结果大于6。

14.有符号数转无符号数

#define Max_CB 500
void LmiQueryCSmd(StructMSgCB * pmsg)
{
 unsigned char ucCmdNum;
 ......  
 for(ucCmdNum=0;ucCmdNum<  Max_CB;ucCmdNum++)
 {
 ......;
 }                    
}
这段代码执行有什么问题?
 死循环。
 unsigned char //无符号字符型表示范围0~255
 char // 有符号字符型 表示范围-128~127

15.C语言程序代码优化方法:

满足需要的情况下,使用尽量小的数据类型

求余运算用与实现(a=a%8改为a=a&7)

用移位实现乘除法运算

switch语句中根据发生频率来进行case排序,对于if,else if语句也同样。

16.如何避免头文件被重复包含:

#ifndef _MY_HEAD_H
#define _MY_HEAD_H    /*空宏*/
/*其他语句*/
#endif

17.局部变量能否和全局变量重名?

能,局部会屏蔽全局。

18.交换两个变量的值,不使用第三个变量。即a=3,b=5,交换之后a=5,b=3;

a = a + b; b = a - b; a = a - b;

或者:

a = a^b;
b = a^b;
a = a^b;

19. . -> [] () * 的优先级问题:

[]、()、.、->这四个运算符同级,且是最高优先级,结合性是从左到右。注意此处的==()是指函数调用or表达式那个括号==,注意与强制类型转换区分。

强制类型转换==()和*属于第二优先级==,结合性是从右到左

记住这两点便能解决以下问题:

int *p[10];//[]的优先级比*高,所以p和[]先结合,表明这是个数组。
int (*p)[10];//加了括号改变了优先级,p和*先结合,表明这是个指针。
//数组指针

下面是先p,再访问p的成员,还是先访问p->data,再取*?

*p->data;
*p.data;

由于->的优先级比高,所以是先p->data,再取
这个例子告诉我们,如果要先*p,需要加括号,即:
(*p)->data

int *fun(int);//由于()的优先级比*高,所以fun先和括号结合,成为函数。//函数,返回值为指针类型
int (*fun)(int);//括号改变了优先级,*和fun结合成为指针。
//函数指针,指针指向一个函数
struct test *a = (struct test *)p->a;

由于->的优先级比强制类型转换的优先级高,所以上面相当于:

struct test *a = (struct test *)(p->a);

20.说出 ((void()())0)(); 的含义:

(*(void(*)())0)();
//从最里层入手,void(*)()表示一种函数指针类型,回想下
 //typedef void(*)() func;
 所以简化成(*(func)0)();  (类型)表示强制类型转换,
 (func)0表示将地址0转换成func类型的函数指针。  
 回想下如何调用函数指针指向的函数,即(*p)();  
串起来就是调用了存放在0地址的函数。

21.int strlen(char s[]) 和 int strlen(char *s)是否等价:

等价。数组和指针在作为形参时,是等价的,可以互换。

22.数组外部类型声明陷阱:

文件1:

char filename[] = "/ect/passwd" ; 

文件2:

extern char* filename;
filename[2] = 't';

这样有什么问题?告诉你,段错误。

保证一个特定的名称的所有外部定义在每个目标模块中具有相同的类型,是程序员的责任,编译器可能检测不到这种错误。正确的声明格式是:

extern char filename[];

23.用两个栈实现一个队列的功能。

要理解栈是先入后出,队列是先进先出。
假设2个栈为A,B, 一开始均为空。
入队: 将新元素push入栈A;
出队:
(1)判断栈B是否为空,如果非空则执行第三步(最开始肯定是空的);
(2)如果为空,则将栈A中所有元素依次pop出并push到栈B(这个操作就将先入后出的顺序颠倒成了符合要求的先进先出)
(3)将栈B的栈顶元素pop出

这样实现的队列入队和出队的平摊复杂度都还是O(1)。

24.嵌入式系统中经常要用到无限循环,你能用C编写多少种死循环呢?:

这个问题有几个解决方案。我首选的方案是:

while(1){
   ...
}

一些程序员更喜欢如下方案:

for(;;){
   ...
}

第三个方案是用goto:

Loop:
        ...
    goto Loop;

25.评价下面的代码片断:

unsigned int compzero = 0xFFFF;`

对于一个int型不是16位的处理器为说,上面的代码是不正确的。应编写如下:
unsigned int compzero = ~0;

26.指针的指针:

下面这段程序会怎样?

void GetMemory(char *p) 
{
    p = (char *)malloc(100);
}
void Test(void)
{
    char *str = NULL;
    GetMemory(str);
    strcpy(str, "hello world");//指针保存地址,4个字节大小
    printf(str);
}

答案当然是段错误,试图修改指针的值,只能传递指针的地址。

27.下面的代码输出是什么,为什么?

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

这个问题测试你是否懂得C 语言中的整数自动转换原则,
当表达式中存在有符号类型和无符号类型时,所有的数都自动转换为无符号类型。因此-20 变成了一个非常大的正整数,所以该表达式计算出的结果大于6 。这一点对于应当频繁用到无符号数据类型的嵌入式系统来说是丰常重要的。

28.关键字static的作用:

1.修饰局部变量的时候:

延长变量的生存周期
在局部变量前面加上关键字static,该局部变量就成了静态局部变量。如果没有初始化,则其默认值为0。

2.修饰全局变量的时候:

在普通全局变量前加关键字static就声明成了静态全局变量。如果没有初始化,则其默认值为0。使用范围为本文件。

29.const的使用:

const忽略类型

const int a;--//定义一个整型常量
int const a;--//定义一个整型常量
const int *a;--//定义一个常量指针,指针可变,指向常量
int * const a;--//定义一个指针常量,指针为常量,指向变量
int const * const a;--//定义一个指针为常量,指向常量

懂得用const的程序员很少会留下的垃圾让别人来清理的。

例如:

int fun(const char *p);//指针指向一个常量,不能够被修改,保护数据

30.关键字volatile作用,以及常用场合。

告诉编译器不要随便优化我的代码,在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。
常用场合:

一个硬件寄存器
中断中用到的变量
线程之间共享变量

31.register关键字的含义和场合:

使用register修饰符有几点限制。

1.register变量必须是能被CPU所接受的类型。这通常意味着register变量必须是一个单个的值,并且长度应该小于或者等于整型的长度。不过,有些机器的寄存器也能存放浮点数。
2.因为register变量可能不存放在内存中,所以不能用“&”来获取register变量的地址。由于寄存器的数量有限,而且某些寄存器只能接受特定类型的数据(如指针和浮点数),因此真正起作用的register修饰符的数目和类型都依赖于运行程序的机器,而任何多余的register修饰符都将被编译程序所忽略。在某些情况下,把变量保存在寄存器中反而会降低程序的运行速度。因为被占用的寄存器不能再用于其它目的;或者变量被使用的次数不够多,不足以装入和存储变量所带来的额外开销。
3.它和volatile是截然相反,所以对于一些可能被硬件修改的变量,谨慎使用。

我在ubuntu上做过尝试,编译时加-O2选项,程序会将频繁使用到的变量做类似于寄存器变量的优化。对于一个for循环,将循环变量或者循环内部操作的变量定义成寄存器变量,可以大大提高效率。

32.请写出bool flag 与“零值”比较的if 语句:

if ( flag )    if ( !flag )

33.请写出int 变量n 与“零值”比较的if 语句:

if ( n == 0 )   if ( n != 0 ) 

34.请写出char *p 与“零值”比较的if 语句:

 if (p == NULL)    if (p != NULL) 

35.以下是求一个数的平方的程序,请找出错误:

#define SQUARE(a) ((a)*(a))
int a = 5;
int b;
b = SQUARE(a++);

宏在预编译时会以直接替换的形式展开。涉及到宏的地方,谨慎使用++ - -,该程序的结果为30。

36.怎么判断单向链表中是否有环?

答:用两个指针来遍历这个单向链表,第一个指针p1,每次走一步;第二个指针p2,每次走两步;当p2 指针追上p1的时候,就表明链表当中有环路了。

int testLinkRing(Link *head)
{
 Link *t1=head,*t2=head;
 while( t1->next && t2->next)
 {
  t1 = t1->next;
  if (NULL == (t2 = t2->next->next))
   return 0; // 无环
  if (t1 == t2)
   return 1;
 }
 return 0;
}

37.switch()的参数类型可以是实型吗?:

不可以。

38.宏和函数的优缺点?

1)、函数调用时,先求出实参表达式的值,然后带入形参。而使用带参数的宏只是进行简单的字符替换。
(2)、函数调用是在程序运行时处理的,分配临时的内存单元;而宏展开则是在编译时进行的,在展开时并不分配内存单元,不进行值的传递处理,也没有“返回值”的概念。
(3)、对函数中的实参和形参都要定义类型,二者的类型要求一致,应进行类型转换;而宏不存在类型问题,宏名无类型,它的参数也是无类型,只是一个符号代表,展开时带入指定的字符即可。宏定义时,字符串可以是任何类型的数据。
(4)、使用宏次数多时,宏展开后源程序长,因为每次展开一次都使程序增长,而函数调用不使源程序变长。
(5)、宏替换不占运行时间,只占编译时间;而函数调用则占运行时间(分配单元、保留现场、值传递、返回)。

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

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

40.对于一个频繁使用的短小函数,在C 语言中应用什么实现,在C++ 中应用什么实现?

c用宏定义,c++ 用inline函数

41.类型分析:

char*(*p[10])(char*,char*);

函数指针数组。

变量名是p,类型为char*([10])(char,char*)
p是数组,数组中元素类型char*()(char,char*)指针
指针char*(char*,char*)

char*(*p[10])(char*,char*)={strcpy,strcat};

调用函数指针数组中的元素:

p[1](buf,buf1)
//strcat(buf,buf1)

42.下面程序段运行的结果:

#include 
int a[5] = {1,2,3,4,5};
int main()
{
 int *ptr=(int *)(&a+1);
 printf("%d\n",*(ptr-1));
 return 0;
}

答案:5
解答:a为存放数组第一个元素地址,&a:二级指针,指向数组
*ptr=(int *)(&a+1);:指向下个数组区域,并强转为一级指针,指向int型
*(ptr-1):代表元素5

43.宏定义:#define的考察:

下面代码执行结果:

#include 
#define MOD(x,y) x%y
int main()
{
 int a=13,b=94;
 printf("%d\n",MOD(b,a+4));
 return 0;
}

答案:7
解答:
94%13+4=7
由于宏定义的时候,其各个分量未加括号,而在使用宏定义的时候,传递的参数是变量的表达式,然后经过系统展开后,由于优先级的原因,导致其结果不是你所希望的.

修改:

#include 
#define MOD(x,y) (x)%(y)
int main()
{
 int a=13,b=94;
 printf("%d\n",MOD(b,a+4));
 return 0;
}

答案:9
94%(13+4)=9

局部变量,全局变量内存
静态存储,动态存储
数组和链表区别
排序方法
TCP/UDP叙述
线程,进程通信方式
三次握手,四次挥手
+宝典+阶段一,足够

你可能感兴趣的:(嵌入式面试知识点汇总,c语言,嵌入式,面试)