嵌入式C笔试总结(长期更新)

来源:互联网 发布:淘宝买家账号信誉查询 编辑:程序博客网 时间:2024/04/30 09:23

声明:本人尚未工作,只是在面试中遇到过一些C语言笔试题目,回来网上搜索发现很多都是网络上点击率很高的题目,这里对于那部分内容就不在赘述,只是总结自己认为不错的,有的是自己总结的,一方面作为自己的知识储备,另一方面拿出来给大家一起分享。。。。

static用法

1、函数之外的变量就是全局变量,这个想法正确吗?
  答: 在C中,完全正确。只不过按有没有加static修饰可以分为静态全局变量和一般全局变量两种。
  2 如果在全局变量的前面加上static 会有什么用?
  答:在C中,静态全局变量意味着两个方面。一、在生命期方面,它与一般全局变量一样,是与整个程序共存亡的;二、在可见性方面,它只在定义它的那个编译单元中可见。比如说,你在testA.c中定义了一个静态全局变量x: static int x;则你只能在testA.c的函数中引用它,在另一个文件testB.c中如果想用它的话:extern int x;然后再某个函数中使用x,则一般编译器在连接阶段会报错说找不到x这个符号。
  3 如果在函数的前面加上static 会有什么用?
  答:与静态全局变量差不多。简单地说,在testA.c中定义的静态函数:static void A(void)是不能在testB.c的函数中调用的,如果一定要这样的话编译器连接时会报错的。
  static的用途:一般对那些非接口函数和确定外部不使用的全局变量加上static限制,可以保证不会被其它编译单元非法使用,同时也可以避免与别的编译单元中的同名符号冲突。在驱动程序中用的较多

详细出处:http://www.52rd.com/Blog/Detail_RD.Blog_qiu3809484_21319.html

 自己总结:简而言之可以说static作用就是限制变量和函数的作用域,限制在定义文件当中。

 const用法

语言中const代表着”不可变“,基本和常量一样不可修改,但是应用场景不一样。

修饰一般常量 一般常量是指简单类型的常量。这种常量在定义时,修饰符const可以用在类型说明符前,也可以用在类型说明符后

一.应用在变量

const char a='A';

a='B';  //错误,变量a的值不可以修改。

此时代表变量a值不可改变,任何企图修改a变量值的语句(例如a=20;)都会报错。

 

二.应用在指针

  1对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const。

A           const   int*   a;   或int   const   *a;//*a是const,但指针a可变  
B           const*   int   a;或int   *   const   a;//a是const,但*a可变 即:指针指向固定的地址,但是此地址中存储的值可变
C           (const   int*   const   a;等价于int   *   const   a   const;)或int   const*   const   a;//a和*a都是const,常量和指针的值都不能改变

 三.应用在函数参数

在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值

    例如3:strcat(char *a,char const *b),将参数b指向的字符串,添加到参数a字符串的末尾。

   此时,参数*a值可以改变,但是表示参数*b值不可改变

http://blog.csdn.net/hanchaoman/article/details/4093368

补充:const对象必须被初始化

定义该const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了

应用const便于进行类型检查,使编译器对处理内容有更多了解,消除了一些隐患

可以保护被修饰的东西,防止意外的修改,增强程序的健壮性

const与define区别:

const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝。 

http://zhidao.baidu.com/question/91670015.html?si=3

 不用关键 字 const,也还是能很容易写出功能正确的程序,那么我为什么还要如此看重关键字const呢?我也如下的几下理由:
1)
关键字const 的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告诉了用户这个参数的应用目的。如果你曾花很多时间清理其它人留下的垃 圾,你就会很快学会感谢这点多余的信息。(当然,懂得用const的程序员很少会留下的垃圾让别人来清理的。)
2)
通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。
3)
合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。
http://hi.baidu.com/www100/blog/item/3c884910e6a1dff7c3ce79a5.html
自己总结:const意味着“只读”,根据const的位置,修饰的变量的“只读”含义不同。

 Volatile用法

这个关键字是区分普通C程序员与嵌入式程序员的一个很好的题目,大牛说的。

volatile前提:由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化

表示用volatile定义的变量会在程序外被改变,每次都必须从内存中读取,而不能把他放在cache或寄存器中重复使用

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

static int i=0;int main(void){...while (1){if (i) dosomething();}}/* Interrupt service routine. */void ISR_2(void){i=1;}

程序的本意是希望ISR_2中断产生时,在main当中调用dosomething函数,但是,由于编译器判断在main函数里面没有修改过i,因此可能只执行一次对从i到某寄存器的读操作,然后每次if判断都只使用这个寄存器里面的“i副本”,导致dosomething永远也不会被调用。如果将将变量加上volatile修饰,则编译器保证对此变量的读写操作都不会被优化(肯定执行)。此例中i也应该如此说明。

一般说来,volatile用在如下的几个地方:

1、中断服务程序中修改的供其它程序检测的变量需要加volatile;

2、多任务环境下各任务间共享的标志应该加volatile;

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

另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过关中断来实现,2中可以禁止任务调度,3中则只能依靠硬件的良好设计了。http://www.laogu.com/wz_692.htm

int *output = (int *)0xff800000; /* 定义一个IO 端口*/volatile int *output = (volatile int *)0xff800000; /* 定义一个IO 端口*/

字符串与指针

printf("%s",str);//%s要与指针或数组名对应,如果用*str就会出现段错误

使用char *p="aaa";那么指针p 和aaa字符都在系统的只读段,常量区,是不能修改的:char *p="aaa",其实和const char *p ="aaa";是一样的;但是如果使用char p[]="aaa",那么就可以修改,在读写段

 

直接访问绝对地址

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

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

A more obscure approach is:
一个较晦涩的方法是:

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


http://hi.baidu.com/www100/blog/item/3c884910e6a1dff7c3ce79a5.html

中断(Interrupts

中 断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展让标准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()经常有重入和性能上的问题。

大端模式与小端模式

大端模式:数据的高位存放于低地址区;

小端模式:数据的高位存放于高地址区。即:大端高对低;小端低对低。

以unsigned int value = 0x12345678为例,分别看看在两种字节序下其存储情况,我们可以用unsigned char buf[4]来表示value:
Big-Endian: 低地址存放高位,如下:

高地址
        ---------------
        buf[3] (0x78) -- 低位
        buf[2] (0x56)
        buf[1] (0x34)
        buf[0] (0x12) -- 高位
        ---------------
 低地址

Little-Endian: 低地址存放低位,如下:

高地址
        ---------------
        buf[3] (0x12) -- 高位
        buf[2] (0x34)
        buf[1] (0x56)
        buf[0] (0x78) -- 低位
        --------------
低地址

大端小端转换方法:

Big-Endian转换成Little-Endian如下:

#define BigtoLittle16(A)                 ((((uint16)(A) & 0xff00) >> 8) | \                                                                   (((uint16)(A) & 0x00ff) << 8))#define BigtoLittle32(A)                 ((((uint32)(A) & 0xff000000) >> 24) | \                                                                   (((uint32)(A) & 0x00ff0000) >> 8) | \                                                                   (((uint32)(A) & 0x0000ff00) << 8) | \                                                                   (((uint32)(A) & 0x000000ff) << 24))

大端小端检测方法:

联合体union的存放顺序是所有成员都从低地址开始存放,利用该特性就可以轻松地获得了CPU对内存采用Little-endian还是Big-endian模式读写。

int checkCPUendian()        {                union                {                        unsigned int a;                        unsigned char b;                 }c;                c.a = 1;                return (c.b == 1);         }         /*return 1 : little-endian, return 0:big-endian*/

程序的存储段

程序分为下面的段:.text, data (initialized), bss, stack, heap。
bss
是英文Block Started by Symbol的简称,通常是指用来存放程序中未初始化的全局变量的一块内存区域,在程序载入时由内核清0。BSS段属于静态内存分配。它的初始值也是由用户自己定义的连接定位文件所确定,用户应该将它定义在可读写的RAM区内,源程序中使用malloc分配的内存就是这一块,它不是根据data大小确定,主要由程序中同时分配内存最大值所确定,不过如果超出了范围,也就是分配失败,可以等空间释放之后再分配。
text段是程序代码段,text段在内存中被映射为只读。在AT91库中是表示程序段的大小,它是由编译器在编译连接时自动计算的,当你在链接定位文件中将该符号放置在代码段后,那么该符号表示的值就是代码段大小,编译连接时,该符号所代表的值会自动代入到源程序中。
data包含静态初始化的数据,所以有初值的全局变量和static变量在data区。段的起始位置也是由连接定位文件所确定,大小在编译连接时自动分配,它和你的程序大小没有关系,但和程序使用到的全局变量,常量数量相关。


stack/heap:
栈(stack)
保存函数的局部变量和参数。是一种“后进先出”(Last In First Out,LIFO)的数据结构,这意味着最后放到栈上的数据,将会是第一个从栈上移走的数据。对于哪些暂时存贮的信息,和不需要长时间保存的信息来说,LIFO这种数据结构非常理想。在调用函数或过程后,系统通常会清除栈上保存的局部变量、函数调用信息及其它的信息。栈另外一个重要的特征是,它的地址空间“向下减少”,即当栈上保存的数据越多,栈的地址就越低。栈(stack)的顶部在可读写的RAM区的最后。
堆(heap)保存函数内部动态分配内存,是另外一种用来保存程序信息的数据结构,更准确的说是保存程序的动态变量。堆是“先进先出”(First In first Out,FIFO)数据结构。它只允许在堆的一端插入数据,在另一端移走数据。堆的地址空间“向上增加”,即当堆上保存的数据越多,堆的地址就越高。

http://springb.itpub.net/post/6448/475648
text和data段都在可执行文件中(在嵌入式系统里一般是固化在镜像文件中),由系统从可执行文件中加载;而bss段不在可执行文件中,由系统初始化

评价或分析代码

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

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

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

 

编程

1、删除串中指定的字符(做此题时,千万不要开辟新空间,否则面试官可能认为你不适合做嵌入式开发)

#include “stdafx.h”void delChar(char *str, char c) { int i, j=0; for(i=0; str[i]; i++)  if(str[i]!=c) str[j++]=str[i]; str[j] = ‘\0′;}int main(int argc, char* argv[]) { char str[] = “abcdefgh”; // 注意,此处不能写成char *str = “abcdefgh”; printf(“%s\n”, str); delChar(str, ‘c’); printf(“%s\n”, str);}
原创粉丝点击