单片机工程师笔试题目归纳汇总

基础知识

波特率
1)定义:

波特率表示每秒钟传送的码元符号的个数,是衡量数据传送速率的指标,它用单位时间内载波调制状态改变的次数来表示。  

在信息传输通道中,携带数据信息的信号单元叫码元,每秒钟通过信道传输的码元数称为码元传输速率,简称波特率。波特率是传输通道频宽的指标。

2)计算:

波特率115200 = 115200 (位/秒)

以最普通的串口(起始位+8位数据+停止位)为例:

除以 10,得到的是每秒字节数:
波特率115200 = 115200 (位/秒) = 11520 (字节/秒) 

再除以 1024,就是每秒 KB 数:
波特率115200 = 115200 (位/秒) = 11.25 (KB/秒) 

如果有一位奇偶校验位,就应该除以 11,得到的是每秒字节数。

最后:

          波特率115200 = 115200 (位/秒) = 10.27 (KB/秒)

备注:

          二进制环境下,波特率 = 比特率。


哈佛结构

哈佛结构是一种将程序指令存储和数据存储分开的存储器结构,它的主要特点是将程序和数据存储在不同的存储空间中,即程序存储器和数据存储器是两个独立的存储器,每个存储器独立编址、独立访问,目的是为了减轻程序运行时的访存瓶颈。

程序指令储存和数据储存分开,数据和指令的储存可以同时进行,可以使指令和数据有不同的数据宽度,如Microchip公司的PIC16芯片的程序指令是14位宽度,而数据是8位宽度。 

其中,51单片机和stm32都为哈佛结构。

冯诺依曼结构

冯·诺依曼结构,又称为普林斯顿体系结构,是一种将程序指令存储器和数据存储器合并在一起的存储器结构。取指令和取操作数都在同一总线上,通过分时复用的方式进行;缺点是在高速运行时,不能达到同时取指令和取操作数,从而形成了传输过程的瓶颈。由于程序指令存储地址和数据存储地址指向同一个存储器的不同物理位置,因此程序指令和数据的宽度相同。 

其中,msp430、ARM7、freescale等单片机为冯诺依曼结构。

常用输出格式类型:
%hd,十进制有符号短整型

%d,十进制有符号整型

%ld,十进制有符号长整型

%hu,十进制无符号短整型

%u,十进制无符号整型

%lu,十进制无符号长整型

%c,字符型

%s,字符串型

%f,十进制浮点型

%lf,十进制双精度浮点型

%e,科学计数法格式

%x,十六进制

%o,八进制

%p,十六进制形式指针

%%,输出'%'字符

选择题

1:设float a=2, b=4, c=3;,以下C语言表达式与代数式 (a+b)+c计算结果不一致的是

  A.(a+b)*c/2

  B.(1/2)*(a+b)*c

  C.(a+b)*c*1/2

  D.c/2*(a+b)

  参考答案:B,因为a,b,c三个变量都是浮点数,B答案(1/2)为整形除以整形,得到的结果也是整形,结果为0;如果改成1/2.0就正确了。代数式中1/2为0.5,其余都是先进行浮点型数据运算或浮点型与整数型运算,得到的结果为浮点型。

2:为了向二进制文件尾部增加数据,打开文件的方式应采用

  A.″ab″

  B.″rb+″

  C.″wb″

  D.″wb+″

  参考答案:D

3:下述程序执行后的输出结果是

  #include

  main()

  {

  int x='f';

  printf("%c\n",'a'+(x-'a'+1));

  }

  A.g

  B.h

  C.i

  D.j

  参考答案:A

4:C语言中,下列运算符优先级最高的是

  A.!

  B.%

  C.>>

  D.= =

  参考答案:A

5:数组定义为“ int a [ 4 ] ; ”,表达式 ( ) 是错误的。

  A.*a

  B.a [ 0 ]

  C.a

  D.a++

  参考答案:D

6:执行语句“ k=7>>1; ”后,变量 k 的当前值是

  A.15

  B.31

  C.3

  D.1

  参考答案:C

7:定义函数时,缺省函数的类型声明,则函数类型取缺省类型

  A.void

  B.char

  C.float

  D.int

  参考答案:D

8:若main()函数带参数,参数个数最多是

  A.0

  B.1

  C.2

  D.3

  参考答案:C 只知道有定义形式main(int argc,char* argv[]))

9:若有宏定义:#define MOD(x,y) x%y

  则执行以下语句后的输出结果是

  int a=13,b=94;

  printf(″%d\n″,MOD(b,a+4));

  A.5

  B.7

  C.9

  D.11

  参考答案:B

10:下列各个错误中,哪一个不属于编译错误

  A.改变 x 原值 3 为 5 ,写作“ x==5 ;”

  B.花括号不配对

  C.复合语句中的最后一条语句后未加分号

  D.变量有引用、无定义

  参考答案:A

11:下列程序段运行后, x 的值是( )

  a=1;b=2;x=0;

  if(!( -- a))x -- ;

  if(!b)x=7;else ++x;

  A.0

  B.3

  C.6

  D.7

  参考答案:A

12:设

  #define N 3

  #define Y(n) ((N+1)*n)

  则表达式2*(N+Y(5+1))的值是

  A.42

  B.48

  C.54

  D.出错

  参考答案:B Y(5+1) 传递过去的应该是6,而不是简单的把5+1给替换掉

13:若定义了char ch[]={″abc\0def″},*p=ch;则执行printf(″%c″,*p+4);语句的输出结果是

  A.def

  B.d

  C.e

  D.0

  参考答案:C

14:下列转义字符中错误的是

  A.′\000′

  B.′\14′

  C.′\x111′

  D.′\2′

  参考答案:C error C2022: '273' : too big for character

15:算术运算符,赋值运算符和关系运算符的`运算优先级按从高到低依次为

  A.算术运算、赋值运算、关系运算

  B.算术运算、关系运算、赋值运算

  C.关系运算、赋值运算、算术运算

  D.关系运算、算术运算、赋值运算

  参考答案:B

16:设#define N 3

  #define Y(n) ((N+1)*n)

  则表达式2*(N+Y(5+1))的值是

  A.42

  B.48

  C.54

  D.出错

  参考答案:B

17:表达式 strcmp( “ 3.14 ”,“ 3.278 ” ) 的值是一个

  A.非零整数

  B.浮点数

  C.0

  D.字符

  参考答案: A

18:设struct

  { short a;

  char b;

  float c;

  }cs;

  则sizeof(cs)的值是

  A.4

  B.5

  C.6

  D.7

  参考答案: D 字节对齐的话应该是8

19:若变量已正确定义,表达式( j=3 , j++ )的值是

  A.3

  B.4

  C.5

  D.0

  参考答案:A

20:C 语言中运算对象必须是整型的运算符是

  A.%

  B./

  C.!

  D.**

  参考答案:A

  简答题

21、在多任务实时系统中,任务间或者中断服务与任务间常需要交换信息,这种信息交换分别为以下哪两个途径()

A、开/关中断和信息邮箱
B、共享存储区和消息传递
C、邮箱机制和数据共享机制
D、非占先式内核和占先式内核机制

22、(多选)有声明

void fun5(int *); 
void fun4(int &a);  
int a;
int *p = &a;

在下列选项中,正确的调用是(  )。
1
2
3
4
5
6
A、fun5(&a);
B、fun5(p);
C、fun4(&a);
D、fun4(p);

答案:A、B
解析:
fun5()的参数是指针,fun4()的参数是普通int
 

23、μC/OS-II操作系统通过事件控制块ECB支持任务间通信。下列说法中错误的是( )。

A、中断服务程序不能等待信号
B、任务可以通过ECB向其他任务发信号
C、多个任务可以同时等待同一事件发生。当该事件发生后,所有等待该事件的任务都能得到该事件并进入运行状态
D、中断服务程序可以通过ECB发信号

答案:C
解析:
μC/OS-II信号量管理的工作原理
μC/OS-II中信号量主要数据结构由两部分组成:(1)信号量的计数值Cnt。当数值为正时用于记录可使用的资源数,当数值为负,其绝对值表示等待当前信号量的任务个数;(2)等待该信号量的任务列表。信号量的基本数据结构需要申请一个ECB来存储。一个任务或ISR可以通过ECB向另外的任务发信号,一个任务可以等待另一个任务或中断服务子程序给它发送信号(中断服务只能发送信号),多个任务可同时等待同一个事件的发生。当事件发生后,等待该事件的优先级最高的任务进入就绪状态,触发一次任务调度。任务或者中断服务子程序都可以给ECB发信号,对ECB进行操作。
 

24、ARM指令完成的功能是如果清除R7中的bit0,1,5位,正确的指令是( )

A、ORR R7,R7,#0x00000023
B、AND R7,R7,#0xFFFFFFDC
C、BIC R7,R7,#0xFFFFFFDC
D、XOR R7,R7,#0xFFFFFFDC

答案:B;
解析
and,按位与,0XDC二进制是1110 1100可见bit0,bit1,bit5位是零。 选择B。

25、8段共阴极LED数码管示意如下图所示,为显示英文字母b,其编码(按dp g f e d c b a的顺序排列)是( )。
在这里插入图片描述

A、01111101
B、01110111
C、01111100
D、01111001

答案:C

26、下面关于UART的叙述中,正确的是( )。

A、UART不能实现全双工通信
B、UART即为通用异步收发器
C、UART通信波特率固定为115200bps,不能调节
D、UART发送字符的长度固定为8位

答案:B

27、一主一从式SPI连接示意如下图所示。从机SPI的4根信号线的名称已在图中标出,为保证主机与从机之间的正确连接及系统正常工作,图中主机的①、②、③、④的信号名称分别应该是什么?


A 、MOSI、MISO、SCK、SSEL
B、SCK、MOSI、MISO、SSEL
C、MISO、MOSI、SCK、SSEL
D、SSEL、MOSI、SCK、MISO

答案:C
解析:
(1)MOSI:主出从入;
(2)MISO:主入从出;

28. int i =1;const int j =2;以下说法不正确的是

A const int *p1 = &i;

B const int *p2 = &j;

C int *const p3 = &i;

D int *const p4 = &j;

D
int *const p4 ,p4为指针常量,p4指向的内存位置不能改变,但是,p4所指内存存放的值是可以改变的。j表示常量,其数值不能被改变。
将j的地址赋给p4后, p4可以执行其他操作( 如*p4=4;),将j的值改变,因此,int *const p4 = &j;是错误的。

29. 32位系统中,该程序的输出为

//参数传递 退化为指针
void Func(char str_arg[100])
{
 printf("%d\n",sizeof(str_arg));
}
int main()
{
 char str[] = "Hello";
 printf("%d\n",sizeof(str));
 printf("%d\n",strlen(str));
 char *p = str;
 printf("%d\n",sizeof(p));
 Func(str);
 return 0;
}

A 5 5 4 4

B 6 5 4 4

C 6 5 6 4

D 5 5 5 100

B 6 5 4 4
使用函数**strlen() 求某个字符串的长度时是不包括结尾标志符'\0' 的,但当你用sizeof()**求某个字符串占用的内存空间时, 结尾字符'\0'是被包括在里面的
strlen用来计算字符串的长度(在C/C++中,字符串是 以"\0"作为结束符的),它从内存的某个位置(可以是字符串开头,中间某个位置,甚至是某个不确定的内存区域)开始扫描直到碰到第一个字符串结束符\0为止,然后返回计数器值。
sizeof是C语言的关键字,它以 字节的形式给出了其操作数的 存储大小,操作数可以是一个表达式或括在括号内的类型名,操作数的存储大小由操作数的类型决定。

30. 有以下程序,求输出结果

#include<stdio.h>
int fun(int i)
{
 int cnt = 0;
 while(i)
 {
  cnt++;
  i=i&(i-1);
 }
 return cnt;
}
int main()
{
 printf("%d\n\r",fun(2021));
 return 0;
}

8
&是按位与,对应位都为1时该位得1,否则得0。所以 i&(i-1) 的作用:将i的二进制表示中的最右边的1置为0。
在本题中即数出2021转换成二进制有几个1就会走几次循环(不断除2)。2021对应的二进制是:10100111111,一共8个1,故走8次。
扩展:(n > 0 && ((n & (n - 1)) == 0)是判断n是不是2的次幂

31. 若 int x = 5&6,那么x的值为()

A 3

B 4

C 5

D 6

B
5: 0101
6: 0110
x: 0100

32. 以下错误的表达式为

struct {
 inr a;
 char b;
}Q,*p=&Q;

A Q.a

B (*p).b

C p->a

D *p.b

D
*p=&Q ,把Q的地址赋值给了指针p,对p解引用其实就是Q。
A 选项肯定是对的,结构体的正常访问方法。
B 选项 (*p).b 等价于 Q.b
C p->a p为指针访问结构体用->没问题。
D *p.b 优先级问题,.的优先级高于 *,所以 *p.b == *(p.b),p为指针,访问结构体成员要用->。

扩展:结构体中.和->两种访问区别
定义结构体指针,访问成员时就用->
定义结构体变量,访问成员时就用.
struct A {
int a;
char b;
};
struct A q; //访问成员就用:q.a;
struct A *p; //访问成员就用:p->a;

33.以下程序的输出结果为

#include <stdio.h>
int main()
{
 int a;
 a=(int)((double)(3/2)+0.5);
 printf("a = %d",a);
    return  0;
}
1
3和2是整形常量,所以3/2=1;前面(double) 1 = 1.000000;1.000000+0.5 = 1.500000;double转int会直接去掉小数部分。所以答案为1。
下面简单分析下double转int为什么会舍去小数部分
根据国际标准 IEEE 754,任意一个二进制浮点数 V 可以表示成下面的形式:
V = (-1) ^ s × M × 2 ^ E
(1)(-1)^s 表示符号位,当 s=0,V 为正数;当 s=1,V 为负数。
(2)M 表示有效数字,大于等于 1,小于 2,但整数部分的 1 不变,因此可以省略。M由frac编码。
(3)2^E 表示指数位。E由exp编码。

对于 64 位的双精度数来说,从低位到高位,尾数 M 用 52 位来表示,阶码用 11 位来表示,而符号位用最高位 1 位来表示,0 表示正,1 表示负。
将1.5转换为双精度浮点数的过程如下:

  1. 将十进制数1.5转换成二进制为1.1。
  2. 1.1用二进制的科学计数法表示为1.1 * 2^0
  3. 按照上面浮点数的存储结构, 得出符号位为: 0,表示正数;阶码(指数) E 为1023; 小数部分 M 为1。
  4. 双 精 度 的 二 进 制 位 : 0_01111111111_0000000000000000000000000000000000000000000000000001

int类型为32位,double转换为int只能截取低32位为00000000000000000000000000000001。
所以最终的输出结果为1。

34.下面这段代码的输出结果为:(A)

#include
void change(int*a, int&b, int c)
{
c=*a;
b=30;
*a=20;
}
int main ( )
{
int a=10, b=20, c=30;
change(&a,b,c);
printf(“%d,%d,%d,”,a,b,c);
return 0;
}

A 20,30,30
B 10,20,30
C 20,30,10
D 10,30,30

解析:
该题考察函数传参问题。
1,指针传参 -> 将变量的地址直接传入函数,函数中可以对其值进行修改。
2,引用传参 -> 将变量的引用传入函数,效果和指针相同。
3,值传参 -> 在传参过程中,首先将c的值复制给函数c变量,然后在函数中修改的即是函数的c变量,然后函数返回时,系统自动释放变量c。而对main函数的c没有影响。
a为指针传递,b为引用传递,c为值传递。
*a = 20; b = 30; a和b的值确实发生了修改。而c只是将值复制到形参,在函数内的操作并不会影响c的实际值,因此c还是为原来的30.


35.有一个如下的结构体:
struct A{
long a1;
short a2;
int a3;
int *a4;
};
请问在64位编译器下用sizeof(struct A)计算出的大小是多少?(A)

A 24
B 28
C 16
D 18
解析:
32位编译器:32位系统下指针占用4字节
char :1个字节
char*(即指针变量): 4个字节(32位的寻址空间是2^32, 即32个bit,也就是4个字节。同理64位编译器)
short int : 2个字节
int: 4个字节
unsigned int : 4个字节
float: 4个字节
double: 8个字节
long: 4个字节
long long: 8个字节
unsigned long: 4个字节
64位编译器:64位系统下指针占用8字节
char :1个字节
char*(即指针变量): 8个字节
short int : 2个字节
int: 4个字节
unsigned int : 4个字节
float: 4个字节
double: 8个字节
long: 8个字节
long long: 8个字节
unsigned long: 8个字节
Win64下:long 8字节、short 2字节、int 4字节、int* 8字节,C++中内存对齐,按最大长度对齐:8+(2+4+2(补齐2字节))+8 = 24字节

36: 考查对volatile关键字的认识
#include
static jmp_buf buf;
main() 
{
volatile int b;
b =3;
if(setjmp(buf)!=0)
{
printf("%d ", b); 
exit(0);
}
b=5;
longjmp(buf , 1);

请问, 这段程序的输出是
(a) 3
(b) 5
(c) 0
(d) 以上均不是

答案解析: (b)

volatile字面意思是易于挥发的。这个关键字来描述一个变量时, 意味着 给该变量赋值(写入)之后, 马上再读取,
写入的值与读取的值可能不一样,所以说它"容易挥发"的。 这是因为这个变量可能一个寄存器, 直接与外部设备相连, 你写入之后,
该寄存器也有可能被外部设备的写操作所改变;或者, 该变量被一个中断程序, 或另一个进程 改变了.

volatile 不会被编译器优化影响, 在longjump 后,它的值 是后面假定的变量值,b最后的值是5,所以5被打印出来.
setjmp : 设置非局部跳转 /* setjmp.h*/

Stores context information such as register values so that the lomgjmp
function can return control to the statement following the one calling
setjmp.Returns 0 when it is initially called.

Lonjjmp: 执行一个非局部跳转 /* setjmp.h*/

Transfers control to the statement where the call to setjmp (which
initialized buf) was made. Execution continues at this point as if
longjmp cannot return the value 0.A nonvolatile automatic variable
might be changed by a call to longjmp.When you use setjmp and longjmp,
the only automatic variables guaranteed to remain valid are those
declared volatile. Note: Test program without volatile qualifier
(result may very)

更详细介绍, 请参阅 C语言的setjmp和longjmp

37:考查类型转换

main()
{
struct node 
{
int a;
int b;
int c; 
};
struct node s= { 3, 5,6 };
struct node *pt = &s;
printf("%d" , *(int*)pt);
}

这段程序的输出是:
(a) 3
(b) 5
© 6
(d) 7

答案解析: (a)

结构题的成员在内存中的地址是按照他们定义的位置顺序依次增长的。如果一个结构体的指针被看成 它的第一个成员的指针,那么该指针的确指向第一个成员

38:考查递归调用

int foo ( int x , int n) 
{
int val;
val =1;
if (n>0) 
{
if (n%2 == 1) val = val *x;
val = val * foo(x*x , n/2);
}
return val;
}

这段代码对x和n完成什么样的功能(操作)?
(a) x^n (x的n次幂)
(b) x*n(x与n的乘积)
© n^x(n的x次幂)
(d) 以上均不是

答案解析: (a)
此题目较难.
这个程序的非递归版本

int what ( int x , int n)
{
int val;
int product;
product =1;
val =x;
while(n>0)
{
if (n%2 == 1) 
product = product*val; 

/如果是奇数次幂, x(val)要先乘上一次,;
偶数次幂, 最后返回时才会到这里乘以1/

val = val* val; 
n = n/2; 
}
return product;
}
/* 用二元复乘策略 */
算法描述
(while n>0) 
{
if next most significant binary digit of n( power) is one
then multiply accumulated product by current val, 
reduce n(power) sequence by a factor of two using integer division.
get next val by multiply current value of itself 
}

39:考查指针,这道题只适合于那些特别细心且对指针和数组有深入理解的人

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

这段程序的输出是:
(a) 2 2
(b) 2 1
© 2 5
(d) 以上均不是

答案解析: ©

a的类型是一个整型数组,它有5个成员。&a的类型是一个整型数组的指针, 所以&a + 1指向的地方等同于a[6], 所以*(a+1)
等同于a[1] ptr等同a[6], ptr-1就等同与a[5]

40:考查多维数组与指针

void foo(int [][3]); 
main()
{
int a [3][3]= { { 1,2,3} , { 4,5,6},{7,8,9}};
foo(a);
printf("%d" , a[2][1]);
}
void foo(int b[][3]) 
{
++ b;
b[1][1] =9;
}

这段程序的输出是:
(a) 8
(b) 9
© 7
(d)以上均不对

答案解析: (b)
题目自身就给了足够的提示

b[0][0] = 4
b[1][0] = 7

41:考查逗号表达式

main()
{
int a, b,c, d;
a=3;
b=5;
c=a,b;
d=(a,b);
printf("c=%d" ,c);
printf("d=%d" ,d);
}

这段程序的输出是:
(a) c=3 d=3
(b) c=5 d=3
© c=3 d=5
(d) c=5 d=5

答案解析: ©

考查逗号表达式,逗号表达式的优先级是很低的, 比 赋值(=)的优先级 低. 逗号表达式的值就是最后一个元素的值
逗号表达式的还有一个作用就是分割函数的参数列表… E1, E2, …, En 上面这个表示式的左右是,E1, E2,…
En的值被分别计算出来, En计算出来的结构赋给整个逗号表达式

c=a,b; / *yields c=a* /
d=(a,b); /* d =b */

42:考查指针数组

main()
{
int a[][3] = { 1,2,3 ,4,5,6};
int (*ptr)[3] = a;
printf("%d %d ", (*ptr)[1], (*ptr)[2]);
++ptr;
printf("%d %d" , (*ptr)[1], (*ptr)[2]);
}

这段程序的输出是:
(a) 2 3 5 6
(b) 2 3 4 5
© 4 5 0 0
(d) 以上均不对

答案解析: (a)

ptr是一个数组的指针, 该数组有3个int成员

43:考查函数指针

int *f1(void)
{
int x =10;
return(&x);
}
int *f2(void)
{
int*ptr;
*ptr =10;
return ptr;
}
int *f3(void)
{
int *ptr;
ptr=(int*) malloc(sizeof(int));
return ptr;
}


上面这3个函数哪一个最可能引起指针方面的问题
(a) 只有 f3
(b) 只有f1 and f3
© 只有f1 and f2
(d) f1 , f2 ,f3

答案解析: ©

f1显然有问题, 它返回一个局部变量的指针, 局部变量是保存在stack中的,退出函数后, 局部变量就销毁了, 保留其指针没有意义,
因为其指向的stack空间可能被其他变量覆盖了 f2也有问题, ptr是局部变量, 未初始化, 它的值是未知的, ptr不知道指向哪里了,
直接给ptr赋值可能会覆盖重要的系统变量, 这就是通常说的野指针的一种

44:考查自加操作(++)

main()
{
int i=3;
int j;
j = sizeof(++i+ ++i);
printf("i=%d j=%d", i ,j);
}

这段程序的输出是:
(a) i=4 j=2
(b) i=3 j=2
© i=3 j=4
(d) i=3 j=6

答案解析: (b)

sizeof 操作符给出其操作数需要占用的空间大小, 它是在编译时就可确定的, 所以其操作数即使是一个表达式, 也不需要在运行时进行计算.(
++i + ++ i )是不会执行的, 所以i的值还是3

45:考查形式参数, 实际参数, 指针和数组

void f1(int *, int); 
void f2(int *, int); 
void(*p[2]) (int *, int);
main()
{
int a;
int b;
p[0] = f1;
p[1] = f2;
a=3;
b=5;
p[0](&a, b);
printf("%d\t %d\t", a, b);
p[1](&a, b);
printf("%d\t %d\t", a, b);
}
void f1(int* p , int q)
{
int tmp;
tmp =*p;
*p = q;
q= tmp;
}
void f2(int* p , int q)
{
int tmp;
tmp =*p;
*p = q;
q= tmp;


这段程序的输出是:
(a) 5 5 5 5
(b) 3 5 3 5
© 5 3 5 3
(d) 3 3 3 3

答案解析: (a)
很显然选a.

f1交换*p 和 q的值, f1执行完后, *p 和 q的值的确交换了, 但q的改变不会影响到b的改变, *p 实际上就是a
所以执行f1后, a=b=5
这道题考查的知识范围很广,包括typedef自定义类型,函数指针,指针数组void(*p[ 2 ]) ( int *, int);
定义了一个函数指针的数组p,p有两个指针元素. 元素是函数的指针, 函数指针指向的函数是一个带2个参数,返回void的函数, 所带的两个参数是指向整型的指针, 和整型

p[ 0 ] = f1; p[ 1 ] = f2 contain address of function .function name without parenthesis represent address of function Value and address of variable is passed to function only argument that is effected is a (address is passed). Because of call by value f1, f2 can not effect b

46:考查自减操作(–)

void e(int ); 
main()
{
int a;
a=3;
e(a);
}
void e(int n)
{
if(n>0)
{
e(--n);
printf("%d", n);
e(--n);
}
}

这段程序的输出是:
(a) 0 1 2 0
(b) 0 1 2 1
© 1 2 0 1
(d) 0 2 1 1

答案解析: (a)

考查–操作和递归调用,仔细分析一下就可以了

47:考查typedef类型定义,函数指针
typedef int (test) ( float * , float)
test tmp;
tmp 的类型是
(a) 函数的指针, 该函数以两个指向浮点数(float)的指针(pointer)作为参数(arguments)
Pointer to function of having two arguments that is pointer to float
(b) 整型
© 函数的指针, 该函数以两个指向浮点数(float)的指针(pointer)作为参数(arguments),并且函数的返回值类型是整型
Pointer to function having two argument that is pointer to float and return int
(d) 以上都不是

答案解析: ©

建议不会的看看C专家编程 从左往有, 遇到括号停下来, 将第一个括号里的东西看成一个整体

48:数组与指针的区别与联系
main()
{
char p;
char buf[10] ={ 1,2,3,4,5,6,9,8};
p = (buf+1)[5];
printf("%d" , p);
}
这段程序的输出是:
(a) 5
(b) 6
© 9
(d) 以上都不对

答案解析: ©

考查什么时候数组就是指针.对某些类型T而言, 如果一个表达式是 T[] (T的数组),
这个表达式的值实际上就是指向该数组的第一个元素的指针.所以(buf+1)[5]实际上就是*(buf +6)或者buf[6]

49: 考查指针数组的指针

void f(char**);
main()
{
char * argv[] = { "ab" ,"cd" , "ef" ,"gh", "ij" ,"kl" };
f( argv );
}
void f( char **p )
{
char* t;
t= (p+= sizeof(int))[-1];
printf( "%s" , t);
}

这段程序的输出是:
(a) ab
(b) cd
© ef
(d) gh

答案解析: (b)

sizeof(int)的值是2,所以p+=sizeof(int) 指向 argv[2],这点估计大家都没有什么疑问
(p+=sizeof(int))[-1] 指向 argv[1],能理解吗, 因为(p+=sizeof(int))[-1] 就相当于
(p+=2)[-1] ,也就是(p+2-1)

50:此题考查的是C的变长参数,就像标准函数库里printf()那样,这个话题一般国内大学课堂是不会讲到的,不会情有可原。

#include 
int ripple ( int , );
main()
{
int num;
num = ripple ( 3, 5,7);
printf( " %d" , num);
}
int ripple (int n, )
{
int i , j;
int k; 
va_list p;
k= 0;
j = 1;
va_start(p, n); 
for (; j{
i = va_arg(p , int);
for (; i; i &=i-1 )
++k;
}
return k;
}
这段程序的输出是:
(a) 7
(b) 6
© 5
(d) 3

答案解析: ©

在C编译器通常提供了一系列处理可变参数的宏, 以屏蔽不同的硬件平台造成的差异,
增加程序的可移植性。这些宏包括va_start、va_arg和va_end等。 采用ANSI标准形式时,
参数个数可变的函数的原型声明是:type funcname(type para1, type para2, …)
这种形式至少需要一个普通的形式参数, 后面的省略号不表示省略, 而是函数原型的一部分。type是函数返回值和形式参数的类型。 不同的编译器,
对这个可变长参数的实现不一样 , gcc4.x中是内置函数. 关于可变长参数,可参阅 /2004-11/26.html
/2004-11/24.html 程序分析 va_list p; /定义一个变量 ,保存 函数参数列表 的指针/ va_start( p
, n); /用va_start宏 初始化变量p, va_start宏的第2个参数n, 是一个固定的参数, 必须是我们自己定义的
变长函数的最后一个入栈的参数也就是调用的时候参数列表里的第1个参数/ for (; j{ i = va_arg( p , int);
/va_arg取出当前的参数,并认为取出的参数是一个整数(int) / for (; i; i &=i-1 )
/判断取出的i是否为0/
++k; / 如果i不为0, k自加, i与i-1进行与逻辑运算, 直到i 为0 这是一个技巧, 下面会谈到它的功能/ } 当我们调用ripple函数时, 传递给ripple函数的 参数列表的第一个参数n的值是3. va_start
初始化p士气指向第一个未命名的参数(n是有名字的参数) ,也就是 is 5 (第一个). 每次对 va_arg的调用, 都将返回一个参数,
并且把 p 指向下一个参数. va_arg 用一个类型名来决定返回的参数是何种类型,以及在
var_arg的内部实现中决定移动多大的距离才到达下一个参数 (; i; i&=i-1) k++ /* 计算i有多少bit被置1 */
5用二进制表示是 (101) 2 7用二进制表示 (111) 3 所以 k 返回 5(2+3),也即本题应该选c 举个例子, 就很好理解了
令 i= 9 = 1001

i-1 = 1000 
(i-1) +1 = i
1000
+1
1001

因为i与i-1的最右边的那位(最低位) 肯定是不同, 如果i1,i-1肯定是0, 反之亦然. i & i-1 这个运算, 在二相补的数字系统中, 将会消除最右边的1位

改错题

1.这段程序有什么问题

#include <stdio.h>
void my_alloc(char **p)
{
    *p = (char *)malloc(100);
}
intm main()
{
    char *otr = NULL;
    char *ptr = NULL;
    my_alloc(&ptr);
    strcpy(ptr,"hello world");
    printf("%s\n",ptr);
    return 0;
}
内存泄漏,分配给其他变量的内存就会减小。 我们在删除一个指针之后,编译器只会释放该指针所指向的内存空间,而不会删除这个指针本身。此时p也就成为一个野指针
扩展:
free()到底释放了什么?
free()释放的是 指针指向的内存!注意,释放的是内存,不是指针
指针是一个变量,只有程序结束时才被销毁。释放了内存空间后,原来指向这块空间的指针还是存在,只不过现在指针指向的内容的垃圾,是未定义的,所以说是垃圾。
因此,释放内存后把指针指向NULL,防止指针在后面不小心又被解引用了。当然,具体情况要具体分析以及具体解决。比如说,你定义了一个指针,在一个函数里申请了一块内存,然后通过函数返回传递给这个指针,那么也许释放这块内存这项工作就应该留给其他函数了。
malloc()到底从哪里得到了内存空间?
从堆里面获得空间。也就是说函数返回的指针是指向堆里面的一块内存。操作系统中有一个记录空闲内存地址的链表。当操作系统收到程序的申请时,就会遍历该链表,然后就寻找第一个空间大于所申请空间的堆结点,然后就将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。

改正程序中的错误

下列给定程序将数组元素循环右移,数组大小和元素以及移动位数由键盘输入指定,例如数组{1,2,3,4,5,6},循环右移三位,得到结果为{4,5,6,1,2,3}

请改正程序中的错误,使它能得出正确的结果。注意:不能更改程序的结构(共有四处错误)

#include<stdio.h>
#include<stdlib.h>
void shift_func(int *array, int len, int k)
{
 int i = 0, j = 0;
 int temp = 0;
 if (array == NULL)
  return;
 k %= len;
 for (i = 0; i < k; i++)
 {
  temp = array[len - 1];
  for (j = len; j > 0; j--)
  {
   array[j] = array[j - 1];
  }
  array[0] = temp;
 }
}
int main()
{
 int len = 0, k = 0, i = 0;
 int *array = NULL;

 scanf("%d%d", &len, &k);
 array = (int*)malloc(len * sizeof(int*));
 if (array == NULL)
  return -1;
 printf("input the array\n");
 for (i = 0; i < len; i++)
  scanf("%d", array[i]);

 shift_func(array, len, k);

 printf("after shift the array is:\n");
 for (i = 0; i < len; i++)
  printf("%d", array[i]);
 printf("\n");

 return 0;
}

修改后的程序如下所示

void shift_func(int *array, int len, int k)
{
 int i = 0, j = 0;
 int temp = 0;
 if (array == NULL)
  return;
 k %= len;
 for (i = 0; i < k; i++)
 {
  temp = array[len - 1];
  for (j = len; j > 0; j--)
  {
   array[j] = array[j - 1];
  }
  array[0] = temp;
 }
}
int main()
{
 int len = 0, k = 0, i = 0;
 int *array = NULL;

 scanf("%d%d", &len, &k);
 array = (int*)malloc(len * sizeof(int));//array = (int*)malloc(len * sizeof(int));
 if (array == NULL)
  return -1;
 printf("input the array\n");
 for (i = 0; i < len; i++)
  scanf("%d", &array[i]);//scanf("%d", &array[i]);

 shift_func(array, len, k);

 printf("after shift the array is:\n");
 for (i = 0; i < len; i++)
  printf("%d", array[i]);
 printf("\n");
       
    // free(array);
    // array = NULL;
 return 0;
}

2、请指出下面程序的错误

unsigned char i = 10; 
char b = 100; 
char* p; 
 
void f(void) 

    While(–-i>=0) 
    { 
       *(p++) = b; 
    } 
}
p没有分配空间 
i是无符号型,自减操作会引发死循环

数据类型
char 一个字节 1 byte
int 两个字节 2 byte (16位系统, 认为整型是2个字节)
long int 四个字节 4 byte
float 四个字节4 byet
double 八个字节 8 byte
long double 十个字节 10 byte
pointer 两个字节 2 byte(注意, 16位系统, 地址总线只有16位)

简答题

1. 用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题) 
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL 
我在这想看到几件事情: 
1). #define 语法的基本知识(例如:不能以分号结束,括号的使用,等等) 
2). 懂得预处理器将为你计算常数表达式的值,因此,直接写出你是如何计算一年中 
有多少秒而不是计算出实际的值,是更清晰而没有代价的。 
3). 意识到这个表达式将使一个16位机的整型数溢出-因此要用到长整型符号L,告诉编 
译器这个常数是的长整型数。 
4). 如果你在你的表达式中用到UL(表示无符号长整型),那么你有了一个好的起点。 
记住,第一印象很重要。


2. 写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个。 
#define MIN(A,B) ((A) <= (B) (A) : (B)) 
这个测试是为下面的目的而设的: 
1). 标识#define在宏中应用的基本知识。这是很重要的,因为直到嵌入(inline)操作符变 
为标准C的一部分,宏是方便产生嵌入代码的唯一方法,对于嵌入式系统来说,为了能达到 
要求的性能,嵌入代码经常是必须的方法。 
2). 三重条件操作符的知识。这个操作符存在C语言中的原因是它使得编译器能产生比if- 
then-else更优化的代码,了解这个用法是很重要的。 
3). 懂得在宏中小心地把参数用括号括起来 
4). 我也用这个问题开始讨论宏的副作用,例如:当你写下面的代码时会发生什么事? 

least = MIN(*p++, b); 

3. 嵌入式系统中经常要用到无限循环,你怎么样用C编写死循环呢? 

这个问题用几个解决方案。我首选的方案是: 
while(1) 


一些程序员更喜欢如下方案: 
for(;;) 


这个实现方式让我为难,因为这个语法没有确切表达到底怎么回事。如果一个应试者给出 
这个作为方案,我将用这个作为一个机会去探究他们这样做的 
基本原理。如果他们的基本答案是:“我被教着这样做,但从没有想到过为什么。”这会 
给我留下一个坏印象。

指针 

4. 用变量a给出下面的定义 
a) 一个整型数(An integer) 
b) 一个指向整型数的指针(A pointer to an integer) 
c) 一个指向指针的的指针,它指向的指针是指向一个整型数(A pointer to a pointer 
to an integer) 
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 fu 
nction 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 r 
eturn an integer ) 

答案是: 
a) int a; // An integer 
b) int *a; // A pointer to an integer 
c) int **a; // A pointer to a pointer to an integer 
d) int a[10]; // An array of 10 integers 
e) int *a[10]; // An array of 10 pointers to integers 
f) int (*a)[10]; // A pointer to an array of 10 integers 
g) int (*a)(int); // A pointer to a function a that takes an integer argument 
and returns an integer 
h) int (*a[10])(int); // An array of 10 pointers to functions that take an int 
eger argument and return an integer 

static
5. 关键字static的作用是什么? 

这个简单的问题很少有人能回答完全。在C语言中,关键字static有三个明显的作用: 
1). 在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。 
2). 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问, 
但不能被模块外其它函数访问。它是一个本地的全局变量。 
3). 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这 
个函数被限制在声明它的模块的本地范围内使用。 
大多数应试者能正确回答第一部分,一部分能正确回答第二部分,同是很少的人能懂得第 
三部分。这是一个应试者的严重的缺点,因为他显然不懂得本地化数据和代码范围的好处 
和重要性。 

const

6.关键字const是什么含意? 
我只要一听到被面试者说:“const意味着常数”,我就知道我正在和一个业余者打交道。 
去年Dan Saks已经在他的文章里完全概括了const的所有用法,因此ESP(译者:Embedded 
Systems Programming)的每一位读者应该非常熟悉const能做什么和不能做什么.如果你从 
没有读到那篇文章,只要能说出const意味着“只读”就可以了。尽管这个答案不是完全的 
答案,但我接受它作为一个正确的答案。(如果你想知道更详细的答案,仔细读一下Saks 
的文章吧。)如果应试者能正确回答这个问题,我将问他一个附加的问题:下面的声明都 
是什么意思? 

const int a; 
int const a; 
const int *a; 
int * const a; 
int const * a const; 

前两个的作用是一样,a是一个常整型数。第三个意味着a是一个指向常整型数的指针(也 
就是,整型数是不可修改的,但指针可以)。第四个意思a是一个指向整型数的常指针(也 
就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。最后一个意味着a是一 
个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不 
可修改的)。如果应试者能正确回答这些问题,那么他就给我留下了一个好印象。顺带提 
一句,也许你可能会问,即使不用关键字const,也还是能很容易写出功能正确的程序,那 
么我为什么还要如此看重关键字const呢?我也如下的几下理由: 
1). 关键字const的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数 
为常量是为了告诉了用户这个参数的应用目的。如果你曾花很多时间清理其它人留下的垃 
圾,你就会很快学会感谢这点多余的信息。(当然,懂得用const的程序员很少会留下的垃 
圾让别人来清理的。) 
2). 通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。 
3). 合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其 
被无意的代码修改。简而言之,这样可以减少bug的出现。 

 volatile

7. 关键字volatile有什么含意 并给出三个不同的例子。 

一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去 
假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读 
取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子: 

1). 并行设备的硬件寄存器(如:状态寄存器) 
2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables) 
3). 多线程应用中被几个任务共享的变量 
回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最 
基本的问题。嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,所用这些都要求vo 
latile变量。不懂得volatile内容将会带来灾难。 
假设被面试者正确地回答了这是问题(嗯,怀疑这否会是这样),我将稍微深究一下,看 
一下这家伙是不是直正懂得volatile完全的重要性。 
1). 一个参数既可以是const还可以是volatile吗?解释为什么。 
2). 一个指针可以是volatile 吗?解释为什么。 
3). 下面的函数有什么错误: 
int square(volatile int *ptr) 

return *ptr * *ptr; 

下面是答案: 
1). 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。 
它是const因为程序不应该试图去修改它。 
2). 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer 
的指针时。 
3). 这段代码的有个恶作剧。这段代码的目的是用来返指针*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; 

8、const常量和#define的区别(编译阶段、安全性、内存占用等)
用#define max 100 ; 定义的常量是没有类型的(不进行类型安全检查,可能会产生意想不到的错误),所给出的是一个立即数,编译器只是把所定义的常量值与所定义的常量的名字联系起来,define所定义的宏变量在预处理阶段的时候进行替换,在程序中使用到该常量的地方都要进行拷贝替换;

用const int max = 255 ; 定义的常量有类型(编译时会进行类型检查)名字,存放在内存的静态区域中,在编译时确定其值。在程序运行过程中const变量只有一个拷贝,而#define所定义的宏变量却有多个拷贝,所以宏定义在程序运行过程中所消耗的内存要比const变量的大得多。

9、变量的作用域(全局变量和局部变量)
全局变量:在所有函数体的外部定义的,程序的所在部分(甚至其它文件中的代码)都可以使用。全局变量不受作用域的影响(也就是说,全局变量的生命期一直到程序的结束)。

局部变量:出现在一个作用域内,它们是局限于一个函数的。局部变量经常被称为自动变量,因为它们在进入作用域时自动生成,离开作用域时自动消失。关键字auto可以显式地说明这个问题,但是局部变量默认为auto,所以没有必要声明为auto。

局部变量可以和全局变量重名,在局部变量作用域范围内,全局变量失效,采用的是局部变量的值。
 

位操作(Bit manipulation) 

10. 嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量a,写两段代码 
,第一个设置a的bit 3,第二个清除a 的bit 3。在以上两个操作中,要保持其它位不变。 


对这个问题有三种基本的反应 
1). 不知道如何下手。该被面者从没做过任何嵌入式系统的工作。 
2). 用bit fields。Bit fields是被扔到C语言死角的东西,它保证你的代码在不同编译器 
之间是不可移植的,同时也保证了的你的代码是不可重用的。我最近不幸看到Infineon为 
其较复杂的通信芯片写的驱动程序,它用到了bit fields因此完全对我无用,因为我的编 
译器用其它的方式来实现bit fields的。从道德讲:永远不要让一个非嵌入式的家伙粘实 
际硬件的边。 
3). 用 #defines 和 bit masks 操作。这是一个有极高可移植性的方法,是应该被用到的 
方法。最佳的解决方案如下: 
#define BIT3 (0x1 < <3) 
static int a; 
void set_bit3(void) 

a |= BIT3; 

void clear_bit3(void) 

a &= ~BIT3; 

一些人喜欢为设置和清除值而定义一个掩码同时定义一些说明常数,这也是可以接受的。 
我希望看到几个要点:说明常数、|=和&=~操作。 

11. 嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。在某工程中,要求 
设置一绝对地址为0x67a9的整型变量的值为0xaa66。编译器是一个纯粹的ANSI编译器。写 
代码去完成这一任务。 
这一问题测试你是否知道为了访问一绝对地址把一个整型数强制转换(typecast)为一指 
针是合法的。这一问题的实现方式随着个人风格不同而不同。典型的类似代码如下: 
int *ptr; 
ptr = (int *)0x67a9; 
*ptr = 0xaa55; 

中断(Interrupts)

中断

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中做浮点运算。此外,ISR应该是短 
而有效率的,在ISR中做浮点运算是不明智的。 
4). 与第三点一脉相承,printf()经常有重入和性能上的问题。如果你丢掉了第三和第四 
点,我不会太为难你的。不用说,如果你能得到后两点,那么你的被雇用前景越来越光明 
了。 


13 . 下面的代码输出是什么,为什么? 
void foo(void) 

unsigned int a = 6; 
int b = -20; 
(a+b > 6) puts("> 6") : puts(" <= 6"); 

这个问题测试你是否懂得C语言中的整数自动转换原则,我发现有些开发者懂得极少这些东 
西。不管如何,这无符号整型问题的答案是输出是“>6”。原因是当表达式中存在有符号 
类型和无符号类型时所有的操作数都自动转换为无符号类型。因此-20变成了一个非常大 
的正整数,所以该表达式计算出的结果大于6。这一点对于应当频繁用到无符号数据类型的 
嵌入式系统来说是丰常重要的。如果你答错了这个问题,你也就到了得不到这份工作的边 
缘。 

14. 评价下面的代码片断: 
unsigned int zero = 0; 
unsigned int compzero = 0xFFFF; 
/*1's complement of zero */ 

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

这一问题真正能揭露出应试者是否懂得处理器字长的重要性。在我的经验里,好的嵌入式 
程序员非常准确地明白硬件的细节和它的局限,然而PC机程序往往把硬件作为一个无法避 
免的烦恼。 
到了这个阶段,应试者或者完全垂头丧气了或者信心满满志在必得。如果显然应试者不是 
很好,那么这个测试就在这里结束了。但如果显然应试者做得不错,那么我就扔出下面的 
追加问题,这些问题是比较难的,我想仅仅非常优秀的应试者能做得不错。提出这些问题 
,我希望更多看到应试者应付问题的方法,而不是答案。不管如何,你就当是这个娱乐吧 
… 

动态内存分配(Dynamic memory allocation) 

15. 尽管不像非嵌入式计算机那么常见,嵌入式系统还是有从堆(heap)中动态分配内存 
的过程的。那么嵌入式系统中,动态分配内存可能发生的问题是什么? 
这里,我期望应试者能提到内存碎片,碎片收集的问题,变量的持行时间等等。这个主题 
已经在ESP杂志中被广泛地讨论过了(主要是 P.J. Plauger, 他的解释远远超过我这里能 
提到的任何解释),所有回过头看一下这些杂志吧!让应试者进入一种虚假的安全感觉后 
,我拿出这么一个小节目:下面的代码片段的输出是什么,为什么? 

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

这是一个有趣的问题。最近在我的一个同事不经意把0值传给了函数malloc,得到了一个合 
法的指针之后,我才想到这个问题。这就是上面的代码,该代码的输出是“Got a valid 
pointer”。我用这个来开始讨论这样的一问题,看看被面试者是否想到库例程这样做是正 
确。得到正确的答案固然重要,但解决问题的方法和你做决定的基本原理更重要些。 

Typedef 

16. Typedef 在C语言中频繁用以声明一个已经存在的数据类型的同义字。也可以用预处理 
器做类似的事。例如,思考一下下面的例子: 
#define dPS struct s * 
typedef struct s * tPS; 

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

第一个扩展为 
struct s * p1, p2; 

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

晦涩的语法 

17. C语言同意一些令人震惊的结构,下面的结构是合法的吗,如果是它做些什么? 
int a = 5, b = 7, c; 
c = a+++b; 


这个问题将做为这个测验的一个愉快的结尾。不管你相不相信,上面的例子是完全合乎语 
法的。问题是编译器如何处理它?水平不高的编译作者实际上会争论这个问题,根据最处 
理原则,编译器应当能处理尽可能所有合法的用法。因此,上面的代码被处理成: 
c = a++ + b; 
因此, 这段代码持行后a = 6, b = 7, c = 12。 
如果你知道答案,或猜出正确答案,做得好。如果你不知道答案,我也不把这个当作问题 
。我发现这个问题的最大好处是:这是一个关于代码编写风格,代码的可读性,代码的可修 
改性。

18. C语言中static函数与普通函数的区别是什么? 
静态函数 
在函数的返回类型前加上关键字static,函数就被定义成为静态函数。 
函数的定义和声明默认情况下是extern的,但静态函数只是在声明他的文件当中可见,不能被其他文件所用。 
定义静态函数的好处: 
<1> 其他文件中可以定义相同名字的函数,不会发生冲突 
<2> 静态函数不能被其他文件所用。 
局部静态变量 
在局部变量之前加上关键字static,局部变量就被定义成为一个局部静态变量。 
1)内存中的位置:静态存储区 
2)初始化:未经初始化的全局静态变量会被程序自动初始化为0(自动对象的值是任意的,除非他被显示初始化) 
3)作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域随之结束。

全局静态变量 
在全局变量之前加上关键字static,全局变量就被定义成为一个全局静态变量。 
1)内存中的位置:静态存储区(静态存储区在整个程序运行期间都存在) 
2)初始化:未经初始化的全局静态变量会被程序自动初始化为0(自动对象的值是任意的,除非他被显示初始化) 
3)作用域:全局静态变量在声明他的文件之外是不可见的。准确地讲从定义之处开始到文件结尾。

好处: 
定义全局静态变量的好处: 
<1>不会被其他文件所访问,修改 
<2>其他文件中可以使用相同名字的变量,不会发生冲突
 

19、关键字volatile有什么含义,并给出几个应用场合。 
volatile的本意是“易变的” 由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化,但有可能会读脏数据。当要求使用volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。 
一般说来,volatile用在如下的几个地方: 
1、中断服务程序中修改的供其它程序检测的变量需要加volatile; 
2、多任务环境下各任务间共享的标志应该加volatile; 
3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;


20、“栈 stack”和“堆 heap”有什么区别,写两个函数分别在栈上和堆上分配内存,在堆上分配内存应注意哪些问题?

1.申请方式 
stack: 
由系统自动分配。 例如,声明在函数中一个局部变量 
int b; 系统自动在栈中为b开辟空间 
heap: 
需要程序员自己申请,并指明大小,在c中malloc函数 
p1 = (char *)malloc(10); 
2.申请后系统的响应 
栈: 
只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。 
堆: 
首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时, 
会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表 
中删除,并将该结点的空间分配给程序. 
3.申请大小的限制 
栈: 
栈顶的地址和栈的最大容量是系统预先规定好的,是一块连续的内存的区域,在WINDOWS下,栈的大小是2M ,如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。 (有限内存) 
堆: 
堆是向高地址扩展的数据结构,是不连续的内存区域。(自己分配)
void fun(void)
{
    int a[10];
    int *p = (int *)malloc(10*sizeof(int));
    if(p == NULL)
    {
        return;
    }
}

21:简述strcpy sprintf与mencpy的区别

三者主要有以下不同之处: (1)操作对象不同,strcpy的两个操作对象均为字符串,sprintf的操作源对象可以是多种数据类型,目的操作对象是字符串,memcpy 的两个对象就是两个任意可操作的内存地址,并不限于何种数据类型。 (2)执行效率不同,memcpy最高,strcpy次之,sprintf的效率最低。 (3)实现功能不同,strcpy主要实现字符串变量间的拷贝,sprintf主要实现其他数据类型格式到字符串的转化,memcpy主要是内存块间的拷贝。 说明:strcpy、sprintf与memcpy都可以实现拷贝的功能,但是针对的对象不同,根据实际需求,来选择合适的函数实现拷贝功能。

22:如何避免“野指针”

“野指针”产生原因及解决办法如下: (1)指针变量声明时没有被初始化。解决办法:指针声明时初始化,可以是具体的地址值,也可让它指向NULL。 (2)指针 p 被 free 或者 delete 之后,没有置为 NULL。解决办法:指针指向的内存空间被释放后指针应该指向NULL。 (3)指针操作超越了变量的作用范围。解决办法:在变量的作用域结束前释放掉变量的地址空间并且让指针指向NULL。 注意:“野指针”的解决方法也是编程规范的基本原则,平时使用指针时一定要避免产生“野指针”,在使用指针前一定要检验指针的合法性。

23:简述指针常量与常量指针区别

指针常量是指定义了一个指针,这个指针的值只能在定义时初始化,其他地方不能改变。其实指针常量是唯一的,即NULL;常量指针是指定义了一个指针,这个指针指向一个只读的对象,不能通过常量指针来改变这个对象的值。 指针常量强调的是指针的不可改变性,而常量指针强调的是指针对其所指对象的不可改变性。 注意:无论是指针常量还是常量指针,其最大的用途就是作为函数的形式参数,保证实参在被调用函数中的不可改变特性。

24:打印一个N*N的方阵,N为每边字符的个数( 3〈N〈20 ),要求最外层为“X”,第二层为“Y”,从第三层起每层依次打印数字0,1,2,3,...

  例子:当N =5,打印出下面的图形:

  X X X X X

  X Y Y Y X

  X Y 0 Y X

  X Y Y Y X

  X X X X X

25:#include

  void main()

  { int c;

  while ((c=getchar())!= ′\ n ′ )

  switch(c - ′ 2 ′ )

  { case 0:

  case 1:putchar(c+4);break;

  case 2:putchar(c+4);break;

  case 3:putchar(c+3);break;

  default:putchar(c+2);break;

  }

  printf( ″\ n ″ );

  }

  运行时输入: 2473 ,输出结果:

  参考答案:6897 VC++6.0测试过

26:用两个栈实现一个队列的功能?

  参考答案:

  设2个栈为A,B, 一开始均为空.入队:

  将新元素push入栈A;出队:

  (1)判断栈B是否为空;

  (2)如果不为空,则将栈A中所有元素依次pop出并push到栈B;

  (3)将栈B的栈顶元素pop出;这样实现的队列入队和出队的平摊复杂度都还是O(1),

27:写一语句实现x是否为2的若干次幂的判断。

  参考答案:!(X)&(X-1)

28:如何判别一个数是unsigned。

  参考答案;

  #define issignal(x) ((x>=0 && ~x>=0) ? 1:0) //为1是无符号 为0有符号

29:执行下述程序的输出结果是 ABCDEFCDEFEF 。

  #include

  char b[]="ABCDEF";

  main()

  {

  char *bp;

  for (bp=b;*bp;bp+=2)

  printf("%s",bp);

  printf("\n");

  }

30:已知一个数组table,用一个宏定义,求出数据的元素个数。

  参考答案:#define NTBL

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

编程题

1、CPU大小端问题

#include <stdio.h>
void main()
{
    union  
    {  
        int a;  
        char b;  
    }c;  
    c.a = 1;
     
    int d;
    d = c.b;
    if(d == 1)
        printf("CPU是小端,c.b = %d\n",c.b);
    if(d == 0)
        printf("CPU是大端,c.b = %d\n",c.b);
    return;
}



2、递归
求阶乘n!

int function1(int n)
{
    if (n == 0)
        return 1;
    while (n >= 1)
    {
        return n*function1(n - 1);
    }
}


int function2(int n)
{
    if (n == 0)
        return 1;
    else
        return n*function2(n - 1);
}



3、输入一个整数(任意位数),倒叙输出:

//计算位数
int length_num(int n)
{
    int num_temp = n;
    int flag = 0;
    while (num_temp > 0)
    { 
        num_temp /= 10;
        flag++;
    }
    return flag;
}
//输入一个整数(任意位数),倒叙输出:
int fun3(int n)
{
    int flag = length_num(n);
    int *p = (int *)malloc(flag * sizeof(int));
    for (int i = 0; i < flag; i++)
    {
        int temp = n % 10;
        *(p + i) = temp;
        n = n / 10;
    }//例如n=123,存储为321
    int n1 = 0;
    for (int i = 0; i < flag; i++)
    {
        n1 = 10 * n1 + (*(p + i));
    }
    free(p);
    p = NULL;
    return n1;
}


4、将一个数组逆序放到原来数组中。(考虑复杂度)

void fun4(int *p, int length)
{
    int n = length / 2;
    for(int i=0;i<n;i++)
    {
        int temp = *(p + i);
        *(p + i) = *(p + length - 1 - i);
        *(p + length - 1 - i) = temp;
    }
}


5、斐波那契数列:

Fib(0) = 0 
Fib(1) = 1 
Fib(n) = Fib(n-1) + Fib(n-2)

F() = 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 …



//斐波那契,使用递归算法复杂度为 O(2n)。

int fun5(int n)
{
    if (n == 0)
        return 0;
    if (n == 1)
        return 1;
    if (n > 1)
        return fun5(n - 1) + fun5(n - 2);
}



//斐波那契,不用递归,算法复杂度O(n)

int fun6(int n)
{
    if (n <= 1)
        return n;
    else
    {
        int forward1 = 0, forward2 = 1;
        int sum = 0;
        for (int i = 1; i < n; i++)
        {
            sum = forward1 + forward2;
            forward1 = forward2;
            forward2 = sum;
        }
        return sum;
    }
}


6、排序
6.1冒泡排序

//冒泡排序,顺序 小---大,比较相邻数,每次循环后,最后的数总是最大
void sort1(int *a, int N)
{
    for (int i = 0; i < N; i++)
    {
        for (int j = 0; j < N-1 - i; j++)
        {
            
            if (a[j + 1] < a[j])
            {
                int temp = a[j];
                a[j] = a[j + 1];
                a[j + 1] = temp;
            }
        }//j
    }//i
}


6.2、选择排序

//选择排序,每次选出i-N中最小值
void sort2(int *a, int N)
{
 
    for (int i = 0; i < N; i++)
    {
        int min_flag = i;
        for (int j = i; j < N; j++)
        {
            if (a[min_flag] > a[j])
            {
                min_flag = j;
            }
 
        }
        int temp = a[i];
        a[i] = a[min_flag];
        a[min_flag] = temp;
    }
}



7、字符串循环移动
编写一个函数,作用是把一个char组成的字符串循环右移n个。比如原来是“abcdefghi”如果n=2,移位后应该是“hiabcdefgh” 函数头是这样的:

//pStr是指向以’\0’结尾的字符串的指针
//steps是要求移动的n
void LoopMove ( char * pStr, int steps )
{
//请填充…
}
//移动字符串循环1
void str_loop(char *p,int N,int loop_n)
{
    //求数组长度,包括\0
    int length = 0;
    for (int i = 0; i < N; i++)
    {
        length++;
        if (p[i] == '\0')
            break;
    }
    char * temp;
    temp = (char*)malloc(length * sizeof(char));//求loop后的temp
    loop_n = loop_n % (length - 1);
    int j = 0;
    for (int i =length-loop_n ; i < length - 1; i++)
    {
        temp[j] = p[i];
        j++;
    }
    for (int i = 0; i < length - loop_n; i++)//temp前半段赋值
    {
        temp[j] = p[i];
        j++;
    }    
    temp[j] = '\0';    
    for (int i = 0; i < length; i++)//temp后半段赋值
    {
        p[i] = temp[i];
    }
    free(temp);
}


8.写一个函数比较两个字符串str1和str2的大小,若相等返回0,若str1大于str2返回1,若str1小于str2返回-1

int strcmp(const char *src,const char *dst)
{
    int ret = 0;
    while(!(ret = *(unsigned char *)src - *(unsigned char *)dst) && *dst)
    {
        src++;
        dst++;
    }
    if(ret<0)
        ret = -1;
    if(ret>0)
        ret = 1;
    return(ret);
}


9.已知strcpy函数的原型是:char * strcpy(char * strDest,const char * strSrc);
不调用库函数,实现strcpy函数。

char *strcpy(char * strDest,const char * strSrc)
{
    assert( (strDest != NULL) && (strSrc != NULL) );
    char *address = strDest; 
    while( (*strDest++ = * strSrc++) != '’ ); 
        return address;
}


10.二分查找算法:

A、递归方法实现:

int BSearch(elemtype a[],elemtype x,int low,int high)
/*在下界为low,上界为high的数组a中折半查找数据元素x*/
{
    int mid;
    if(low>high)
        return -1;
    mid=(low+high)/2;
    if(x == a[mid])
        return mid;
    if(x<a[mid])
        return(Bsearch(a,x,low,mid-1));
    else
        return(BSearch(a,x,mid+1,high));
}


B、非递归方法实现:

int BSearch(elemtype a[],keytype key,int n)
{
    int low,high,mid;
    low=0;high=n-1;
    while(low<=high)
    {
        mid=(low+high)/2;
        if(a[mid].key==key)
            return mid;
        else if(a[mid].key<key)
            low=mid+1;
        else
            high=mid-1;
    }
    return -1;
}


11.输入字符倒叙输出,输入’#'结束

#include<stdio.h>

int main()
{
    char *ch;
    char a;
    int i = 0;
    puts("输入字符倒叙输出,输入'#'结束:");
    while((a = getchar()) != '#')
    {
        *ch = a;
        ch++;
        i++;
    }
    for(i;i>0;i--)
    {
        ch--;
        printf("%c",*ch);
    }
    printf("\n");
    return 0;
}


12.链表的逆序

#include<stdio.h>
#include<malloc.h>

typedef struct Node
{
    int data;
    struct Node *next;
}Node;

Node* LinkList()
{
    Node *head,*r,*p;
    int i = 1;
    head = (Node *)malloc(sizeof(Node));
    r = head;
    puts("输入节点数据,输入字符结束:");

    while(i)
    {
        p = (Node *)malloc(sizeof(Node));
        i = scanf("%d",&p -> data);
        if(i != 0)
        {
            r -> next = p;
            r = p;
        }
    }
    r -> next = NULL;
    return(head);
}

void print(Node* L)
{
    puts("print:");
    L = L -> next;
    while(L != NULL)
    {
        printf("%d ",L -> data);
        L = L -> next;
    }
    puts("");
}

void reverseOutputLinklist(Node* L)
{
    Node *r,*p;
    r = L -> next;
    L -> next = NULL;
    while(r != NULL)
    {
        p = r;
        r = r -> next;
        p -> next = L -> next;
        L -> next = p;
    }
}

void main()
{
    Node* a;
    a = LinkList();
    print(a);
    reverseOutputLinklist(a);
    puts("reverseOutputLinklist");
    print(a);
    return;
}


13.快速排序

#include <stdio.h>

void swap(int *a,int *b)
{
    int temp;
    temp = *a;
    *a = *b;
    *b = temp;
}

void quicksort(int array[],int begin,int end)
{
    int i = begin;
    int j = end;
    int pivotkey;
    
    if(begin < end)
    {
        pivotkey = array[i];
        while(i < j)
        {
            while((i < j)&&(array[j] >= pivotkey))
                j--;
            swap(&array[i],&array[j]);
            while((i < j)&&(array[i] <= pivotkey))
                i++;
            swap(&array[i],&array[j]);
        }
        
        quicksort(array,begin,i);
        quicksort(array,i+1,end);
    }
}

void main()
{
    int i = 0;
    int a[15] = {6,5,8,1,16,3,19,87,56,4,64,11,24,52,7};
    for(i = 0;i < 15;i++)
        printf("%d ",a[i]);
    printf("\n");
    quicksort(a,0,14);
    for(i = 0;i < 15;i++)
        printf("%d ",a[i]);
    printf("\n");
}


14.16进制字符串转10进制数

#include<stdio.h>
#include<string.h>

int fun(char *a)
{
    int len = 0;
    int i = 0;
    long b = 0;
    while((*(a+len)) != '\0')
    {
        len++;
    }
    for(;i<len;i++)
    {
        if((*(a+i))>='a'&&(*(a+i))<='z')
            b = (b*16)+((*(a+i))-('a')+10);
        if((*(a+i))>='0'&&(*(a+i))<='9')
            b = (b*16)+((*(a+i))-('0'));
    }
    return(b);
}

void main(void)
{
    int a,c,len,i;
    char b[10];
    scanf("%x",&a);
    sprintf(b,"%x",a);
    c = fun(b);
    printf("%d\n",c);
}


15.字符串压缩
eg:输入“aassssdccccvvttt”,输出“2a4sd4c2v3t”

#include <stdio.h>
#include<string.h>

int count(int j);
char zip(char *stri,int len,char *stro);

void main()
{
    int len;
    char stri[100]={},stro[100]={};
    scanf("%s",stri);
    len = strlen(stri);
    zip(stri,len,stro);
    printf("%s\n",stro);
}

int count(int j)
{
    int count = 0;
    while(j>0)
    {
        count++;
        j/=10;
    }
    return(count);
}

char zip(char *stri,int len,char *stro)
{
    int i = 0,k = 0;
    int j = 1;
    char flag = 0;
    
    while(i<len)
    {
        if((*(stri+i)) != flag)
        {
            flag = *(stri+i);
            i++;
        }
        while(flag == *(stri+i))
        {
            j++;
            i++;
        }
        if(j>1)
        {
            sprintf((stro+k),"%d",j);
            k = k+count(j);
            j = 1;
        }
        *(stro+k) = flag;
        k++;
    }
}


16、不使用 if,swtich,?: ,输出a,b中的较大值

//a、b两者之和加上a、b的差值除以二即为a、b中的较大值

#include<main.h>

int fun(int a,int b)

{
    int sum = a+b;
    int dif = sab(a-b);    //sab()为求绝对值函数
    int max = (sum+dif)/2;

    return(max);
}

17. 编写strcat函数
已知strcat函数的原型是char *strcat (char *strDest,const char *strSrc);
其中strDest 是目的字符串,strSrc 是源字符串。
(1)不调用C++/C 的字符串库函数,请编写函数 strcat
(2)strcat能把strSrc 的内容连接到strDest,为什么还要char * 类型的返回值?

答案:

(1)源码:

char * strcat (char * dst, const char * src)
{
char * cp = dst;
while( *cp )
cp++; /* find end of dst */
while( *cp++ = *src++ ) ; /* Copy src to end of dst */
return( dst ); /* return dst */
}

(2)方便赋值给其他变量

18. 请实现内存复制函数void memcpy(void *dst const void *src,int size) 

void *memcpy(void *dest, const void *src, unsigned int size)
{
 
    if(dest==NULL || src==NULL)
    {
        return dest;
    }
    
    void *temp = dest;
 
    while(size--)
    {
        *(char *)dest = *(char *)src;
        dest = (char *)dest + 1;
        src = (char *)src + 1;
    }
 
    return temp;
}

19.输入年,月,日,计算这一天是该年的第几天。年份符合以下两种条件的任意种即为闰年,闰年里2月会有29天:

1.年份是4的倍数但不是100的倍数。

2.年份是40的倍数

输入描述:

输入表示日期的格式: Year, Month, Day包含三个整数:年(1<=Year3000),月(1<= Month<=12),日(1<=Day<=31)

思路:比较容易想到的一种方法是查表。将一年中每个月份的天数放进数组中,数组下标索引即代表月份。

这里要注意闰年的处理。为了方便,我们定义两个数组,分别对应闰年的天数和非闰年的天数。再定义一个变量flag来判断是否为闰年即可。具体代码如下所示。

/*
 * @Description: 北京联发科嵌入式软件工程师笔试题目
 * @Version: 
 * @Autor: 嵌入式与Linux那些事
 * @Date: 2021-3-15 22:24:12
 * @LastEditors: 嵌入式与Linux那些事
 * @LastEditTime: 2021-3-15 22:39:41
 */
#include<stdio.h>
//两个数组,分别存放闰年和非闰年每个月的天数,对应 1~ 12月
int b1[12]= {31,28,31,30,31,30,31,31,30,31,30,31};
int b2[12]= {31,29,31,30,31,30,31,31,30,31,30,31};

/**
 * @Description: 累加天数
 * @Param: months,月份。flag,闰年判断标志位
 * @Return: 天数
 * @Author: 公众号【嵌入式与Linux那些事】
 */
int add(int months,int flag,int days)
{
    int i,j = 0;
    if(flag)
    {
        for(i=0; i<months-1; i++)
        {
            j+=b1[i];
        }
		j+=days;
    }
    else
    {
        for(i=0; i<months-1; i++)
        {
            j+=b2[i];
        } 
		j+=days;
    }
    return j;
}

int main()
{
    int years,months,days;
    //设置标志位判断闰年
    int flag = 0;
    scanf("%d,%d,%d",&years,&months,&days);
    
    if(years>=1 && years<=3000 && months>=1 && months<=12 && days>=1 && days<=31)
    {
        if((years%4==0&&years%100!=0))
        {
            flag = 1;
        }
        printf("result is %d",add(months,flag,days));
    }
    else
    {
        printf("invalid parameter");
    }
    return 0;
}

  • 16
    点赞
  • 113
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值