嵌入式常见 c语言笔试题

c语言笔试题

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

答:

#define  SENCONDS_IN_ONE_YREAR  (365 * 12 * 60 * 60)ul

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

答:

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

3, 你怎么样用C编写死循环呢?

答:

for(;;)死循环里的两个;;代表两个空语句,编译器一般会优化掉它们,直接进入循环体。

while(true)死循环里的true被看成表达式,每循环一次都要判断表达式是否真。

LOOP:

goto LOOP;

4, 用变量a给出下面的定义:

答:

  1. 一个整型数; int a;

  2. 一个指向整型数的指针;int *a;

  3. 一个指向指针的指针,它指向的指针是指向一个整型数;int **a;

  4. 一个有10个整型数的数组;int a[10];

  5. 一个有10个指针的数组,该指针是指向一个整型数的;int *a[10];

  6. 一个指向有10个整型数数组的指针;int (*a)[10];

  7. 一个指向函数的指针,该函数有一个整型数参数并返回一个整数;int (*a)(int);

  8. 一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数; int (*a[10]) (int);

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

答:

static 修饰

函数内局部变量:改变存储区域,变量存储在静态存储区,函数内部定义的变量在退出函数后再次进入保持原来的值不变。

全局变量和全局函数:将函数限定在定义该函数的文件内,外部文件不可访问。

成员变量:所有对象只保存一个该变量,不需要生成对象就可以访问该变量。

成员函数:使不需要声明对象就可以访问该函数。函数内不可以访问非static的变量。

6,关键字const有什么含意?(只读)

答:

const 修饰变量:表示定义的是一个常量,不可以被改变。

const 修饰指针:分为指向常量的指针和常量指针。

const 修饰引用:即避免了拷贝,又避免了函数对值的修改;

const 修饰成员函数:表时该函数内不可以改变成员变量的值。

下面的声明都是什么意思?

答:

1)const int a; 定义一个整型变量a,a是一个常整型数,不可以被修改。

2)int const a; 同1。

3)const int *a; 定义一个指向长整型的变量a,整型数不可以修改。

4)int *const a; 定义一个指向整型的常指针,指针不可修改。

5)int const *a const; a是一个指向常整型数的常指针,都不可修改。

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

答:

void foo(void)

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

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

8, 预处理器标识#error的目的是什么?

答:

#error 停止编译并显示错误信息,当程序比较大时,往往有些宏定义是在外部指定的(如makefile),或是在系统头文件中指定的,配合#if使用可以提示编程人员某宏定义是否已经被定义。

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

答:

一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
  1) 并行设备的硬件寄存器(如:状态寄存器)
  2) 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
  3) 多线程应用中被几个任务共享的变量

​ 1)一个参数既可以是const还可以是volatile吗?解释为什么。

​ 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。

2); 一个指针可以是volatile 吗?解释为什么。

​ 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个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;
    }

10,嵌入式系统总是要用户对变量或寄存器进行位操作。给定一个整型变量a,写两段代码,第一个设置a的bit 3,第二个清除a 的bit 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;

A more obscure approach is:
  一个较晦涩的方法是:
    *(int * const)(0x67a9) = 0xaa55;
  即使你的品味更接近第二种方案,但我建议你在面试时使用第一种方案。

A more obscure approach is:
  一个较晦涩的方法是:
    *(int * const)(0x67a9) = 0xaa55;
  即使你的品味更接近第二种方案,但我建议你在面试时使用第一种方案。

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

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

答:

​ 1)ISR 不能返回一个值。如果你不懂这个,那么你不会被雇用的。
  2) ISR 不能传递参数。如果你没有看到这一点,你被雇用的机会等同第一项。
  3) 在许多的处理器/编译器中,浮点一般都是不可重入的。有些处理器/编译器需要让额处的寄存器入栈,有些处理器/编译器就是不允许在ISR中做浮点运算。 此外,ISR应该是短而有效率的,在ISR中做浮点运算是不明智的。
4) 与第三点一脉相承,printf()经常有重入和性能上的问题。如果你丢掉了第三和第四点,我不会太为难你的。不用说,如果你能得到后两点,那么你的被雇 用 前景越来越光明了。

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

    **
        **unsigned int zero = 0;**
        **unsigned int compzero = 0xFFFF;**
        /*1's complement of zero */
    

    答:

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

14,下面的代码片段的输出是什么,为什么?

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

答:

代码也能通过编译,但事实上只分配了0个字节大小的内存空间,当你往里头存入一个整数,就会有3个字节无家可归,结果是后面的内存中原有数据内容被改写。这样做是很危险的。这种错误也可以自己测试出来,虽然写入动态空间,但是在释放动态空间是没法释放的,因为free函数不能释放别人的空间。

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

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

以上两种情况的意图都是要定义dPS 和 tPS 作为一个指向结构s指针。哪种方法更好呢?(如果有的话)为什么?

以上两种情况的意图都是要定义dPS 和 tPS 作为一个指向结构s指针。哪种方法更好呢?(如果有的话)为什么?

答:

这是一个非常微妙的问题,任何人答对这个问题(正当的原因)是应当被恭喜的。答案是:typedef更好。思考下面的例子:

   dPS p1,p2;
    tPS p3,p4;

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

16 . C语言同意一些令人震惊的结构,下面的结构是合法的吗,如果是它做些什么?

    int a = 5, b = 7, c;
    c = a+++b;

c的值是多少?

答:

上面的代码被处理成:c = a++ + b; 因此, 这段代码持行后a = 6, b = 7, c = 12。

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

答:

能,局部会屏蔽全局。要用全局变量,需要使用"::" ;局部变量可以与全局变量同名,在函数内引用这个变量时,会用到同名的局部变量,而不会用到全局变量。对于有些编译器而言,在同一个函数内可以定义多个同名的局部变量,比如在两个循环体内都定义一个同名的局部变量,而那个局部变量的作用域就在那个循环体内。

18、如何引用一个已经定义过的全局变量?

答:

答:extern  可以用引用头文件的方式,也可以用extern关键字,如果用引用头文件方式来引用某个在头文件中声明的全局变理,假定你将那个编写错了,那么在编译期间会报错,如果你用extern方式引用时,假定你犯了同样的错误,那么在编译期间不会报错,而在连接期间报错。

19,全局变量可不可以定义在可被多个.C文件包含的头文件中?为什么?

答:

可以,在不同的C文件中以static形式来声明同名全局变量。变量的定义只能出现一次,否则会导致重复定义。但却可以声明多次。因此全局变量不可以定义在头文件中。因为当该头文件被多个c文件包含的话,会导致重复定义。因此一般做法是在某个特定的头文件中声明,而在另外一个特定的c文件中定义。需要使用就包含前者。

20,程序的局部变量存在于(堆栈)中,全局变量存在于(静态区 )中,动态申请数据存在于( 堆)中。

21,设有以下说明和定义:

typedef union
{
    
    
long i;
int k[5];
 char c;
} DATE;  
struct data
{
    
    
  int cat;
  DATE cow;
  double dog;
} too;  
DATE max;  

则语句 printf("%d",sizeof(struct data)+sizeof(max));的执行结果是:52_

考点:区别struct与union.(一般假定在32位机器上)

答:

DATE是一个union, 变量公用空间. 里面最大的变量类型是int[5], 占用20个字节. 所以它的大小是20. data是一个struct, 每个变量分开占用空间. 依次为int4 + DATE20 + double8 = 32. 所以结果是 20 + 32 = 52. 当然…在某些16位编辑器下, int可能是2字节,那么结果是 int2 + DATE10 + double8 = 20

21、队列和栈有什么区别?

答:队列先进先出,栈后进先出

22,请找出下面代码中的所有错误 (题目不错,值得一看)

说明:以下代码是把一个字符串倒序,如“abcd”倒序后变为“dcba”

#include"string.h"   
main()   
{
    
       
char*src="hello,world";   
char* dest=NULL;   
int len=strlen(src);
dest=(char*)malloc(len);  // char* dest = (char*)malloc(**len+1)**;/要为分配一个空间     
char* d=dest;   
char* s=src[len];       //**char* s = &src[len-1];**  指向最后一个字符
while(len--!=0)   
d++=s--;  //尾部要加’\0’
printf("%s",dest);   
return 0;   
}  

答:

free(dest); // 使用完,应当释放空间,以免造成内存汇泄露

dest = NULL;   //防止产生野指针

23.软件测试都有那些种类?

黑盒:针对系统功能的测试

白盒:测试函数功能,各函数接口

24,

unsigned char *p1;

unsigned long *p2;

p1=(unsigned char *)0x801000; 

p2=(unsigned long *)0x810000;

请问p1+5= ;

p2+5= ;

答案:0x801005(相当于加上5位) 0x810014(相当于加上20位);

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

#define SQUARE(a)((a)*(a))

int a=5;
int b;

b=SQUARE(a++);

答:结果与编译器相关,得到的可能不是平方值.

26.C语言中为什么不能支持函数重载?

答:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uUUCJCJV-1608009983155)(笔试题.assets/20180619173607995)]

从上图可知编译器在编译.c文件时,只会给函数进行简单的重命名;具体的方法是给函数名之前加上”_”;所以加入两个函数名相同的函数在编译之后的函数名也照样相同;调用者会因为不知道到底调用那个而出错;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p9VhFZEV-1608009983162)(笔试题.assets/20180619173625713.png)]

在.cpp文件中,虽然两个函数的函数名一样,但是他们在符号表中生成的名称不一样。

  1. new、delete、malloc、free关系

delete会调用对象的析构函数,和new对应free只会释放内存,new调用构造函数。malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。

28 描述内存分配方式以及它们的区别?

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

2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集。

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

  1. 请说出const与#define 相比,有何优点?

答案:

const作用:定义常量、修饰函数参数、修饰函数返回值三个作用。被Const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。

1) const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。

2) 有些集成化的调试工具可以对const 常量进行调试,但是不能对宏常量进行调试。

30,将程序跳转到指定内存地址

要对绝对地址0x100000赋值,我们可以用(unsigned int*)0x100000 = 1234;那么要是想让程序跳转到绝对地址是0x100000去执行,应该怎么做?

*((void ( * )( ))0x100000 ) ( );
  首先要将0x100000强制转换成函数指针,:
  (void (*)())0x100000
  然后再调用它:
  *((void ( * )())0x100000)();
  用typedef可以看得更直观些:
  typedef void( * )() voidFuncPtr;
  *((voidFuncPtr)0x100000)();

31,判断大端小端

int CheckCPU()
{
    
    
	union w
	{
    
    
		int a;
		char b;
	}c;
	c.a = 1;
	return (c.a == 1)
}

返回值是0,则是大端,返回值是1,则是小端。0x00 00 00 01

0x12345678 大端:12(0x4000) 34(0x4001) 56(0x4002) 78(0x4003)

0x12345678 小端:78(0x4000) 56(0x4001) 34(0x4002)12(0x4003)

32.分别写出BOOL,int,float,指针类型的变量a 与“零”的比较语句。

答案:

BOOL    : if ( !a ) or if(a)
int     : if ( a == 0)
float   : const EXPRESSION EXP = 0.000001
		  if ( a < EXP && a >-EXP)
pointer : if ( a != NULL) or if(a == NULL)

33,判断32位整数二进制中1的个数的算法

int count_one_bits(int n)
{
    
    
	int count=0;
	while(n)
	{
    
    
		count++;
		n=n&(n-1);
	}
	return count;
}

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

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

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

1)ISR 不能返回一个值。如果你不懂这个,那么你不会被雇用的。
2) ISR 不能传递参数。如果你没有看到这一点,你被雇用的机会等同第一项。
3) 在许多的处理器/编译器中,浮点一般都是不可重入的。有些处理器/编译器需要让额处的寄存器入栈,有些处理器/编译器就是不允许在ISR中做浮点运算。此外,ISR应该是短而有效率的,在ISR中做浮点运算是不明智的。
4) 与第三点一脉相承,printf()经常有重入和性能上的问题。如果你丢掉了第三和第四点,我不会太为难你的。不用说,如果你能得到后两点,那么你的被雇用前景越来越光明了。

猜你喜欢

转载自blog.csdn.net/qq_15555275/article/details/111204250