C语言测试是招聘嵌入式系统程序员过程中必须而且有效的方法。这些年,我既参加也组织了许多这种测试,在这过程中我意识到这些测试能为带面试者和被面试者提供许多有用信息,此外,撇开面试的压力不谈,这种测试也是相当有趣的。
从被面试者的角度来讲,你能了解许多关于出题者或监考者的情况。这个测试只是出题者为显示其对ANSI标准细节的知识而不是技术技巧而设计吗?这个愚蠢的问题吗?如要你答出某个字符的ASCII值。这些问题着重考察你的系统调用和内存分配策略方面的能力吗?这标志着出题者也许花时间在微机上而不上在嵌入式系统上。如果上述任何问题的答案是”是”的话,那么我知道我得认真考虑我是否应该去做这份工作。
从面试者的角度来讲,一个测试也许能从多方面揭示应试者的素质:最基本的,你能了解应试者C语言的水平。不管怎么样,看一下这人如何回答他不会的问题也是满有趣。应试者是以好的直觉做出明智的选择,还是只是瞎蒙呢?当应试者在某个问题上卡住时是找借口呢,还是表现出对问题的真正的好奇心,把这看成学习的机会呢?我发现这些信息与他们的测试成绩一样有用。
有了这些想法,我决定出一些真正针对嵌入式系统的考题,希望这些令人头痛的考题能给正在找工作的人一点帮住。这些问题都是我这些年实际碰到的。其中有些题很难,但它们应该都能给你一点启迪。
60、局部变量能否和全局变量重名?
答:能,局部会屏蔽全局。要用全局变量,需要使用 ":: " 局部变量可以与全局变量同名,在函数内引用这个变量时,会用到同名的局部变量,而不会用到全局变量。
对于有些编译器而言,在同一个函数内可以定义多个同名的局部变量,比如在两个循环体内都定义一个同名的局部变量,
而那个局部变量的作用域就在那个循环体内。
61、如何引用一个已经定义过的全局变量?
答:extern 可以用引用头文件的方式,也可以用extern关键字,如果用引用头文件方式来引用某个在头文件中声明的全局变理,
假定你将那个变量写错了,那么在编译期间会报错,如果你用extern方式引用时,假定你犯了同样的错误,
那么在编译期间不会报错,而在连接期间报错。
62、全局变量可不可以定义在可被多个.C文件包含的头文件中?为什么? 答:可以,在不同的C文件中以static形式来声明同名全局变量。 可以在不同的C文件中声明同名的全局变量,前提是其中只能有一个C文件中对此变量赋初值,此时连接不会出错
63、语句for( ;1 ;)有什么问题?它是什么意思? 答:和while(1)相同。
64、static全局变量与普通的全局变量有什么区别?static局部变量和普通局部变量有什么区别?static函数与普通函数有什么区别? 全局变量(外部变量)的说明之前再加static 就构成了静态的全局变量。全局变量本身就是静态存储方式,
静态全局变量当然也是静态存储方式。 这两者在存储方式上并无不同。
这两者的区别虽在于非静态全局变量的作用域是整个源程序, 当一个源程序由多个源文件组成时,
非静态的全局变量在各个源文件中都是有效的。 而静态全局变量则限制了其作用域,
即只在定义该变量的源文件内有效。 从以上分析可以看出,把局部变量改变为静态变量后是改变了它的存储
方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域,
限制了它的使用范围。static函数与普通函数作用域不同。仅在本文件。只在当前源文件中使用的函数应该说明为内部函数(static)
,内部函数应该在当前源文件中说明和定义。对于可在当前源文件以外使用的函数,应该在一个头文件中说明,
要使用这些函数的源文件要包含这个头文件 static全局变量与普通的全局变量有什么区别:
static全局变量只初使化一次,防止在其他文件单元中被引用static局部变量和普通局部变量有什么区别:
static局部变量只被初始化一次,下一次依据上一次结果值; static函数与普通函数有什么区别:
static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝
65、程序的局部变量存在于(堆栈)中,全局变量存在于(静态区 )中,动态申请数据存在于( 堆)中。
66、-1,2,7,28,,126请问28和126中间那个数是什么?为什么?
答案应该是4^3-1=63 规律是n^3-1(当n为偶数0,2,4)n^3+1(当n为奇数1,3,5)
67、用两个栈实现一个队列的功能?要求给出算法和思路!
设2个栈为A,B, 一开始均为空.
入队: 将新元素push入栈A;
出队: (1)判断栈B是否为空;
(2)如果不为空,则将栈A中所有元素依次pop出并push到栈B;
(3)将栈B的栈顶元素pop出;
68、.软件测试都有那些种类?
人工测试:个人复查、抽查和会审
机器测试:黑盒测试(针对系统功能的测试 )和白盒测试 (测试函数功能,各函数接口)
69、堆栈溢出一般是由什么原因导致的?
没有回收垃圾资源。
70、写出float x 与“零值”比较的if语句。
if(x>0.000001&&x<-0.000001)
71、P地址的编码分为哪俩部分?
IP地址由两部分组成,网络号和主机号。不过是要和“子网掩码”按位与上之后才能区分哪些是网络位哪些
是主机位
72、写一个程序, 要求功能:求出用1,2,5这三个数不同个数组合的和为100的组合个数。
如:100个1是一个组合,5个1加19个5是一个组合。。。。 请用C++语言写。
答案:最容易想到的算法是:
设x是1的个数,y是2的个数,z是5的个数,number是组合数
注意到0<=x<=100,0<=y<=50,0<=z=20,所以可以编程为:
number=0;
for (x=0; x<=100; x++)
for (y=0; y<=50; y++)
for (z=0; z<=20; z++)
if ((x+2*y+5*z)==100)
number++;
73、内存对齐问题的原因?
平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据;
性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐,因为为了访问未对齐的内存,处理器需要做两次内存访问,
而对齐的内存访问仅需要一次。
74、比较一下进程和线程的区别?
(1)、调度:线程是CPU调度和分派的基本单位
(2)、拥有资源:
* 进程是系统中程序执行和资源分配的基本单位
* 线程自己一般不拥有资源(除了必不可少的程序计数器,一组寄存器和栈),但他可以去访问其所属进程的资源,
如进程代码,数据段以及系统资源(已打开的文件,I/O设备等)。
(3)系统开销:
* 同一进程中的多个线程可以共享同一地址空间,因此它们之间的同步和通信的实现也比较简单
* 在进程切换的时候,涉及到整个当前进程CPU环境的保存以及新被调度运行的进程的CPU环境的设置;
而线程切换只需要保存和设置少量寄存器的内容,并不涉及存储器管理方面的操作,从而能更有效地使用系统资源和
提高系统吞吐量。
75、main()
{
int a[5]={1,2,3,4,5};
int *ptr=(int *)(&a+1);//&a相当于变成了行指针,加1则变成了下一行首地址
printf("%d,%d",*(a+1),*(ptr-1));
}
*(a+1)就是a[1],*(ptr-1)就是a[4],执行结果是2,5
76、 void getmemory(char *p)
{
p=(char *) malloc(100);
strcpy(p,"hello world");
}
int main( )
{
char *str=NULL;
getmemory(str);
printf("%s/n",str);
free(str);
return 0;
}
程序崩溃,getmemory中的malloc 不能返回动态内存, free()对str操作很危险
77、void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
请问运行Test函数会有什么样的结果?
答:程序崩溃。因为GetMemory并不能传递动态内存,Test函数中的 str一直都是 NULL。strcpy(str, "hello world");将使程序崩溃。
void GetMemory2(char **p, int num)
{
*p = (char *)malloc(num);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
请问运行Test函数会有什么样的结果?
答:(1)能够输出hello
(2)内存泄漏
78、char *GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
请问运行Test函数会有什么样的结果?
答:可能是乱码。因为GetMemory返回的是指向“栈内存”的指针,该指针的地址不是 NULL,
但其原现的内容已经被清除,新内容不可知。
79、void Test(void)
{
char *str = (char *) malloc(100);
strcpy(str, “hello”);
free(str);
if(str != NULL)
{
strcpy(str, “world”);
printf(str);
}
}
请问运行Test函数会有什么样的结果?
答:篡改动态内存区的内容,后果难以预料,非常危险。因为free(str);之后,str成为野指针,
if(str != NULL)语句不起作用。
野指针不是NULL指针,是指向被释放的或者访问受限内存指针。
造成原因:指针变量没有被初始化任何刚创建的指针不会自动成为NULL;
指针被free或delete之后,没有置NULL;
指针操作超越了变量的作用范围,比如要返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放。
80、unsigned char *p=(unsigned char *)0x0801000
unsigned char *q=(unsigned char *)0x0810000
p+5 =? 0x0801005
q+5 =? 0x0810005
81、进程间通信方式:管道、命名管道、消息队列、共享内存、信号、信号量、套接字。
(1)、 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。
进程的亲缘关系通常是指父子进程关系。
(2)、有名管道 (named pipe) :有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
(3)、信号量( semophore ) :信号量是一个计数器,可以用来控制多个进程对共享资源的访问。
它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。
因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
(4)、消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。
消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
(5)、信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
(6)、共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,
这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,
如信号两,配合使用,来实现进程间的同步和通信。
(7)、套接字( socket ) : 套接字也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
82、宏和函数的优缺点?
(1)、函数调用时,先求出实参表达式的值,然后带入形参。而使用带参数的宏只是进行简单的字符替换。
(2)、函数调用是在程序运行时处理的,分配临时的内存单元;而宏展开则是在编译时进行的,在展开时并不分配内存单元,
不进行值的传递处理,也没有“返回值”的概念。
(3)、对函数中的实参和形参都要定义类型,二者的类型要求一致,应进行类型转换;而宏不存在类型问题,宏名无类型,
它的参数也是无类型,只是一个符号代表,展开时带入指定的字符即可。宏定义时,字符串可以是任何类型的数据。
(4)、调用函数只可得到一个返回值,而宏定义可以设法得到几个结果。
(5)、使用宏次数多时,宏展开后源程序长,因为每次展开一次都使程序增长,而函数调用不使源程序变长。
(6)、宏替换不占运行时间,只占编译时间;而函数调用则占运行时间(分配单元、保留现场、值传递、返回)。
83、C和c++的不同
c和c++的一些不同点(从语言本身的角度):
1)c++源于c,c++最重要的特性就是引入了面向对象机制,class关键字。
2)c++中,变量可以再任何地方声明;c中,局部变量只能在函数开头声明。
3)c++中,const型常量是编译时常量;c中,const常量只是只读的变量。
4)c++有&引用;c没有
5)c++的struct声明自动将结构类型名typedef;c中struct的名字只在结构标签名字空间中,不是作为一种类型出现
6)c语言的main函数可以递归调用;c++中则不可以
7)c中,void *可以隐式转换成其他指针类型;c++中要求现实转换,否则编译通不过
84、6.大小端格式问题。
方法一:
void checkCpuMode(void)
{
int i = 0x12345678;
char *cp = (char *)&i;
if(*cp == 0x78)
printf("little endian");
else
printf("big endian\n");
}
方法二:
void checkCpuMode(void)
{
int a = 0x12345678;
if((char)a == 0x12)
printf("big endian\n");
else
printf("little endian\n");
}
方法三:
void checkCpuMode(void)
{
union
{
short s;
char c[sizeof(short)];
}un;
un.s=0x0102;
if(un.[0]==1&&un.c[1]==2)
printf("big endian\n");
else
printf("little endian\n");
}
85、由C/C++编译的程序占用的内存分为以下几个部分
1、栈区(stack): 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2、堆区(heap): 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。
注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
3、全局区(static): 全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,
未初始化的全局变量和未初始化的静态变量在相邻的另一块区域,程序结束后有系统释放 。
4、文字常量区: 常量字符串就是放在这里的, 程序结束后由系统释放。
5、程序代码区: 存放函数体的二进制代码。
87、for(m=5;m--;m<0)
{
printf("m=%d\n",m);
}输出:4、3、2、1、0
88、5["abcdef"]能够编译通过,请问编译后的结果是什么?
printf("%d\n",5["abcdef"]);输出'f'的ACSII值,如果是4["abcdef"]则输出'e'的ACSII的值。
89、线程同步的方法:信号量、条件变量、互斥锁。
90、for(i=0;i<2,i<3,i<4;i++)
printf("%d \n",i); 输出:0,1,2,3。
100、物理地址,虚拟地址,逻辑地址和总线地址的区别
逻辑地址(Logical Address)是指由程序产生的与段相关的偏移地址部分。
例如,你在进行C语言指针编程中,可以读取指针变量本身值(&操作),实际上这个值就是逻辑地址,
它是相对于你当前进程数据段的地址,不和绝对物理地址相干。只有在Intel实模式下,
逻辑地址才和物理地址相等(因为实模式没有分段或分页机制, Cpu不进行自动地址转换);
逻辑也就是在Intel 保护模式下程序执行代码段限长内的偏移地址(假定代码段、数据段如果完全一样)。
应用程序员仅需与逻辑地址打交道,而分段和分页机制对您来说是完全透明的,仅由系统编程人员涉及。
应用程序员虽然自己可以直接操作内存,那也只能在操作系统给你分配的内存段操作。
线性地址(Linear Address)是逻辑地址到物理地址变换之间的中间层。程序代码会产生逻辑地址,
或者说是段中的偏移地址,加上相应段的基地址就生成了一个线性地址。如果启用了分页机制,
那么线性地址可以再经变换以产生一个物理地址。若没有启用分页机制,那么线性地址直接就是物理地址。
Intel 80386的线性地址空间容量为4G(2的32次方即32根地址总线寻址)。
物理地址(Physical Address) 是指出现在CPU外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果地址。
如果启用了分页机制,那么线性地址会使用页目录和页表中的项变换成物理地址。
如果没有启用分页机制,那么线性地址就直接成为物理地址了。
在x86下,外设的i/o地址是独立的,即有专门的指令访问外设i/o,i/o地址就是你所说的“总线地址”。
而“物理地址”就是ram地址。在arm中,i/o和ram统一编址,但linux为了统一各个平台,仍然保留这个概念,其实就是物理地址。
101、编写内核程序中申请内存和编写应用程序时申请内存有什么区别
应用程序使用C函数库中的内存分配函数malloc()申请内存内核会为进程使用的代码和数据空间维护一个当前位置的值brk,
这个值保存在每个进程的数据结构中。它指出了进程代码和数据(包括动态分配的数据空间)在进程地址空间中的末端位置。
当malloc()函数为程序分配内存时,它会通过系统调用brk()把程序要求新增的空间长度通知内核,
内核代码从而可以根据malloc()所提供的信息来更新brk的值,但此时并不为新申请的空间映射物理内存页面。
只有当程序寻址到某个不存在对应物理页面的地址时,内核才会进行相关物理内存页面的映射操作。
当用户使用内存释放函数free()动态释放已申请的内存块时,c库中的内存管理函数就会把所释放的内存块标记为空闲,
以备程序再次申请内存时使用。在这个过程中内核为该进程所分配的这个物理页面并不会被释放掉。
只有当进程最终结束时内核才会全面收回已分配和映射到该进程地址空间范围内的所有物理内存页面。
102、如何用C语言实现读写寄存器变量
#define rBANKCON0 (*(volatile unsigned long *)0x48000004)
rBankCON0 = 0x12;
103、sscanf("123456 ", "%4s", buf); 1234
sscanf("123456 asdfga","%[^ ]", buf); 123456
sscanf("123456aafsdfADDEFF", "%[1-9a-z]", buf); 123456aafsdf
sscanf("123afsdfADJKLJ", "%[^A-Z]", buf); 123afsdf
104、char* s="AAA";
s[0]='B';
printf("%s",s);
有什么错?
"AAA"是字符串常量。s是指针,指向这个字符串常量,所以声明s的时候就有问题。
cosnt char* s="AAA";
然后又因为是常量,所以对是s[0]的赋值操作是不合法的。
105、数组a[N],存放了1至N-1个数,其中某个数重复一次。写一个函数,找出被重复的数字.时间复杂度必须为o(N)函数原型:
int do_dup(int a[],int N) int do_dup(int a[],int N)//a[0]为监视哨
{ {
int i; int temp;
int s; while (a[0]!=a[a[0]])
int num; {
for(i=0;i<N;i++) temp=a[0];
s+=a[i]; a[0]=a[temp];
num=s-N*(N-1)/2;(num即为重复数) a[temp]=temp;
} }
return a[0];
}
106、tcp/udp是属于哪一层?tcp/udp有何优缺点?
tcp /udp属于运输层
TCP 服务提供了数据流传输、可靠性、有效流控制、全双工操作和多路复用技术等。
与 TCP 不同, UDP 并不提供对 IP 协议的可靠机制、流控制以及错误恢复功能等。由于 UDP 比较简单, UDP 头包含很少的字节,比 TCP 负载消耗少。
tcp: 提供稳定的传输服务,有流量控制,缺点是包头大,冗余性不好
udp: 不提供稳定的服务,包头小,开销小
107、 char a = 100;
char b = 150;//10010110//01101010
unsigned char c ;
c = (a < b)? a:b; --》 c=150;
108、应用程序ping发出的是ICMP请求报文
109、在C语言中memcpy和memmove是一样的吗?
memmove()与memcpy()一样都是用来拷贝src所指向内存内容前n个字节到dest所指的地址上,不同是,当src和dest所指的内存区域重叠时,
memmove()仍然可以正确处理,不过执行效率上略慢些。
110、C语言程序代码优化方法
* 选择合适的算法和数据结构
* 使用尽量小的数据类型
* 使用自加、自减指令
* 减少运算的强度
求余运算(a=a%8改为a=a&7)
平方运算(a=pow(a,2.0)改为a=a*a)
用移位实现乘除法运算
* 延时函数的自加改为自减
* switch语句中根据发生频率来进行case排序
111、找出一个字符串中一个最长的连续的数字,并标注出位置和个数。
void main()
{
char input[100];
char output[100] = {0};
int count = 0, maxlen = 0, i = 0;
char *in=input, *out=output,*temp=NULL,*final=NULL;
printf("Please input string(length under 100):\n");
scanf("%s", input);
printf("Input string is %s\n", input);
while(*in!='\0')
{
if(*in>='0'&&*in<='9')
{
count=0;
temp=in;
for(;(*in>='0')&&(*in<='9');in++)
count++;
if (maxlen<count)
{
maxlen=count;
=temp;
}
}
in++;
}
for(i=0; i<maxlen; i++)
*(out++) = *(final++);
*out='\0';
printf("Maxlen is %d\n", maxlen);
printf("Output is %s\n", output);
}
112、写出螺旋矩阵
void Matrix(int m,int n) //顺时针
{
int i,j,a=1;
int s[100][100];
int small = (m<n)?m:n;
int k=small/2;
for(i=0;i<k;i++)
{
for(j=i;j<n-i-1;j++)
s[i][j]=a++;
for(j=i;j<m-i-1;j++)
s[j][n-i-1]=a++;
for(j=n-i-1;j>i;j--)
s[m-i-1][j]=a++;
for(j=m-i-1;j>i;j--)
s[j][i]=a++;
}
if(small & 1)
{
if(m<n)
for(i=k;i<n-k;++i)
s[k][i]=a++;
else
for(i=k;i<m-k;++i)
s[i][k]=a++;
}
for(i=0;i<m;i++)
{
for(j=0;j<n;j++)
printf("=",s[i][j]);
printf("\n");
}
}
113、int *a = (int *)2;
printf("%d",a+3); 答案是2+3*4=14;int类型地址加1 相当于加4个字节
114、main()
{
char a,b,c,d;
scanf("%c%c",&a,&b);
c=getchar();d=getchar();
printf("%c%c%c%c\n",a,b,c,d);
} 输出:12
3
115、int a[2] = {1, 2};
//指向常量的指针,指针可以变,指针指向的内容不可以变
const int *p = a;//与int const *p = a;等价
p++;//ok
*p = 10;//error
//常指针,指针不可以变,指针指向的内容可以变
int* const p2 = a;
p2++;//error
*p2 = 10;//ok
//指向常量的常指针,都不可以改变
p3++;//error
*p3 = 10;//error
116、#error 预处理指令的作用是,编译程序时,只要遇到#error 就会生成一个编译错误提
示消息,并停止编译
117、中断活动的全过程大致为:
1、中断请求:中断事件一旦发生或者中断条件一旦构成,中断源提交“申请报告”,
与请求CPU暂时放下目前的工作而转为中断源作为专项服务
2、中断屏蔽:虽然中断源提交了“申请报告”,但是,是否得到CPU的响应,
还要取决于“申请报告”是否能够通过2道或者3道“关卡”(中断屏蔽)送达CPU
(相应的中断屏蔽位等于1,为关卡放行;反之相应的中断屏蔽位等于0,为关卡禁止通行);
3、中断响应:如果一路放行,则CPU响应中断后,将被打断的工作断点记录下来
(把断点地址保护到堆栈),挂起“不再受理其他申请报告牌”
(清除全局中断标志位GIE=0),跳转到中断服务子程序
4、保护现场:在处理新任务时可能破坏原有的工作现场,所以需要对工作现场和工作环境进行适当保护;
5、调查中断源:检查“申请报告”是由哪个中断源提交的,以便作出有针对性的服务;
6、中断处理:开始对查明的中断源进行有针对性的中断服务;
7、清除标志:在处理完毕相应的任务之后,需要进行撤消登记(清除中断标志),以避免造成重复响应;
8、恢复现场:恢复前面曾经被保护起来的工作现场,以便继续执行被中断的工作;
9、中断返回:将被打断的工作断点找回来(从堆栈中恢复断点地址),
并摘下“不再受理其他申请报告牌”(GIE=1),继续执行原先被打断的工作。
118、linux进程间通讯的几种方式的特点和优缺点,和适用场合。分类:
#管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,
而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
#有名管道(named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
#信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。
它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作
为进程间以及同一进程内不同线程之间的同步手段。
#消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识
符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
#信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
#共享内存( shared memory):共享内存就是映射一段能被其他进程所访问的内存,
这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的IPC方式,
它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,
如信号量,配合使用,来实现进程间的同步和通信。
#套接字( socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,
它可用于不同及其间的进程通信。
119、关键字volatile有什么含意?并给出三个不同的例子。
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设
这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,
而不是使用保存在寄存器里的备份。
下面是volatile变量的几个例子:
1). 并行设备的硬件寄存器(如:状态寄存器)
2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3). 多线程应用中被几个任务共享的变量 回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,所用这些都要求volatile变量。不懂得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;
}
Volatile 关键字告诉编译器不要持有变量的临时性拷贝。一般用在多线程程序中,
以避免在其中一个线程操作该变量时,将其拷贝入寄存器。
请看以下情形: A线程将变量复制入寄存器,然后进入循环,反复检测寄存器的值是否满足
一定条件(它期待B线程改变变量的值。在此种情况下,当B线程改变了变量的值时,
已改变的值对其在寄存器的值没有影响。所以A线程进入死循环。
volatile 就是在此种情况下使用。
120、如何防止同时产生大量的线程,方法是使用线程池,线程池具有可以同时提高调度效率和
限制资源使用的好处,线程池中的线程达到最大数时,其他线程就会排队等候
121、main()
{
char *p1=“name”;
char *p2;
p2=(char*)malloc(20);
memset (p2, 0, 20);
while(*p2++ = *p1++);
printf(“%s\n”,p2);
}
答案:Answer:empty string. 因p2++已经指到了'\0'了;
122、操作系统的内存分配一般有哪几种方式,各有什么优缺点?
定长和变长。
变长:内存时比较灵活,但是易产生内存碎片。
定长:灵活性差,但分配效率较高,不会产生内存碎片
123、全局变量可不可以定义在可被多个.C文件包含的头文件中?为什么?
答:可以,在不同的C文件中以static形式来声明同名全局变量。可以在不同的C文件中声明同名的全局变量,
前提是其中只能有一个C文件中对此变量赋初值,此时连接不会出错
124、确定模块的功能和模块的接口是在软件设计的那个队段完成的?概要设计阶段
125、#define N 500
unsigned char count;
for(count=0;count < N;count++)
{
printf("---%d---\n",count);
}死循环,因为unsigned char 最大为255
126、给定结构struct A
{
char t:4; 4位
char k:4; 4位
unsigned short i:8; 8位
unsigned long m; // 偏移2字节保证4字节对齐
}; // 共8字节
127、ICMP(ICMP协议对于网络安全具有极其重要的意义)功能主要有:
· 侦测远端主机是否存在。
· 建立及维护路由资料。
· 重导资料传送路径。
· 资料流量控制。
单向、双向链表操作、写一个快速排序算法(原理:找一个基准值,分别将大于和小于基准值的数据放到基准值左右两边,即一次划分。由于处在两边的数据也是无序的,所以再用同样的划分方法对左右两边的序列进行再次划分,直到划分元素只剩1个时结束,)
/******************************************************************/
1、单向链表逆序
LONDE *link_reverse(LNODE *head)
{
LNODE *pb,*pt;
if(head == NULL)
return head;
pb = head->next;
head->next=NULL;
while(pb != NULL)
{
pt = pb->next;
pb->next = head;
head = pb;
pb = pt;
}
return head;
}
2、快速排序
void quick_sort(int num,int start_num,int end_num)
{
if(start_num < end_num)
{
int i = start_num;
int j = end_num;
int temp = num[start_num];
while(i < j)
{
while(i < j && num[j] < temp)
j--;
if(i < j)
num[i++] = num[j];//把小于基准值放在左边
while(i < j && num[i] >= temp)
i++;
if(i < j)
num[j--] = num[i];//把大于基准值放在右边
}
num[i] = temp;
quick_sort(num,start_num,i-1);
quick_sort(num,i+1,end_num);
}
}
3、//二分擦找
int binary_search(int array[],int value,int size)
{
int low=0,high=size-1,mid;
while(low<=high) //只要高低不碰头就继续二分查找
{
mid=(low+high)/2;
if(value==array[mid]) //比较是不是与中间元素相等
return mid;
else if(value > array[mid]) //每查找一次,就判断一次所要查找变量所在范围,并继续二分
low=mid; //如果大小中间值,下限移到中间的后一个位,上限不变,往高方向二分
else
high=mid; //上限移到中间的前一个位,往低方向二分
}
return -1;
}
/*双向循环链表插入函数*/
TYPE *insert_link(TYPE *head,TYPE *p_in)
{
TYPE *p_mov = head,p_front = head;
if(head == NULL)
{
head = p_in;
p_in->next = head;
p_perior = head;
}
else
{
while((p_in->[] > p_mov->[]) && (p_mov->next != head))
{
p_front = p_mov;
p_mov = p_mov->next;
}
if(p_in->[] <= p_mov->[])
{
if(head == p_mov)
{
p_in->prior = head->prior;
head->prior->next = p_in;
p_in->next = p_mov;
p_mov->prior = p_in;
head = p_in;
}
else
{
pf->next = p_in;
p_in->prior = p_front;
p_in->next = p_mov;
p_mov->prior = p_in;
}
}
else
{
p_mov->next = p_in;
p_in->prior = p_mov;
p_in->next = head;
head->prior = p_in;
}
}
return head;
}
/*双向链表删除函数*/
TYPE *delete_link(TYPE *head,int num)
{
TYPE *p_mov = head,p_front = head;
if(head == NULL)
printf("Not link\n");
while((p_mov->num != num) && (p_mov->next != head))
{
p_front = p_mov;
p_mov = p_mov->next;
}
if(p_mov->num == num)
{
if(p_mov == head)
{
if(p_mov->next == head && p_mov->prior == head)
{
free(pb);
head =NULL;
return head;
}
head->next->prior = head->prior;
head->prior->next = head->next;
head = head->next;
}
else
{
p_front->next = p_mov->next;
p_mov->next->prior = p_front;
}
free(p_mov);
printf("The node is delete\n");
}
else
{
printf("The node not been found\n");
}
return head;
}
-END-