嵌入式linux开发面试题解析(二)——C语言部分一
1、编写统计一个数二进制表示中有多少个1的函数
int count_bit1(int m)
{
int count = 0;
while(m)
{
m = m & (m-1);
count++;
}
return count;
}
2、编写一个函数判断一个数是否是2的N次方
int is_number(int num)
{
if( m & (m - 1) == 0)
return 0;//如果一个数是2的N次方,返回0
else
return 1;//如果一个数不是2的N次方,返回1
}
3、用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)
#define SECONDS_PER_YEAR (1UL*60 * 60 * 24 * 365)
兼容16位CPU, #define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL这种用法在GCC是不能编译通过的。
4、预处理器标识#error的目的是什么?
#error error-message停止编译并显示错误信息,error-message不需要双引号
5、嵌入式系统中经常要用到无限循环,你怎么样用C编写死循环呢?
while(1)
{
;
}
6、用变量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 function that takes an integer as anargument and returns an integer)
h)一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数( An array of ten pointers to functions that take aninteger argument and return 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]; // Anarray of 10 pointers to integers
等价于int *(a[10]);
f) int (*a)[10]; // Apointer to an array of 10 integers
g) int (*max_function)(int a); // A pointer to afunction 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 integer argument andreturn an integer
7、键字static的作用是什么?
在C语言中,关键字static有三个明显的作用:
A、一旦声明为静态变量,在编译时刻开始永远存在,不受作用域范围约束,但是如果是局部静态变量,则此静态变量只能在局部作用域内使用,超出范围不能使用,但是它确实还占用内存,还存在.
B、在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
C、在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。
8、typedef与define的区别
typedef是C语言中用来声明自定义数据类型,配合各种原有数据类型来达到简化编程的目的的类型定义关键字。
#define是预处理指令,是宏定义。
int a = 80;
typedef int * PINT;
const PINT p = &a;//const修饰指针类型,限定指针变量p为只读
PINT const p = &a;//const修饰指针类型,限定指针变量p为只读
9、k输出的值是多少
int main(int argc, char *argv[])
{
char k = 0;
int i;
for(i = 0; i < 127; i++)
k += i & 3;
printf("k = %d\n", k);
return 0;
}
i:0 1 2 3 4 5 6 7
i & 3:0 1 2 3 0 1 2 3
k:0 1 2 3 6 6 7 9
i共计有32组0 1 2 3和一组0 1组成
因此k=32 *(0 + 1 + 2 + 3)+(0 + 1)= 187
由于k为char,所以k在127以后的数是-127,-126....,所以
K = (187-127)+ (-127)= -67;
输出-67;
10、嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。
在某工程中,要求设置一绝对地址为0x67a9的整型变量的值为
0xaa66。编译器是一个纯粹的ANSI编译器。写代码去完成这一任务。
解答出如下:
int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa55;
嵌入式开发中常用应用:
#define rPCONA(*(volatile unsigned *)0x1D20000)
#define rPDATA(*(volatile unsigned *)0x1D20004)
11、下列代码输出结果是多少
char str1[] = "abc";
char str2[] = "abc";
const char str3[] = "abc";
const char str4[] = "abc";
const char *str5 = "abc";
const char *str6 = "abc";
char *str7 = "abc";
char *str8 = "abc";
cout << ( str1 == str2 ) << endl;
cout << ( str3 == str4 ) << endl;
cout << ( str5 == str6 ) << endl;
cout << ( str7 == str8 ) << endl;
解答:
输出结果是:0 0 1 1
解析:str1,str2,str3,str4是数组变量,有各自的内存空间;而str5,str6,str7,str8是指针,指向相同的常量区域。
12、简介##和#的作用
预处理器运算符##是连接符号,由两个井号组成,其功能是在带参数的宏定义中将两个子串(token)联接起来,从而形成一个新的子串。但它不可以是第一个或者最后一个子串。所谓的子串(token)就是指编译器能够识别的最小语法单元。如果替换文本中的参数与##相邻,则该参数将被实际参数替换,##与前后的空白将被删除,并对替换后的结果重新扫描。 形成一个新的标号,如果这样产生的记号无效,或者结果依赖于##运算顺序,则结果没有定义。
#definepaste(front,back)front##back
宏调用paste(name,_xiaobai)的结果为name_xiaobai
#符是把传递过来的参数当成字符串进行替代
#definedprint(expr)printf(#expr"=%d\n",expr)
int a=20,b=10;
dprint(a/b);
打印出: a/b=2
13、关键字volatile有什么含意?并给出三个不同的例子。
定义为volatile的变量表明变量可能会被意想不到地改变,编译器就不会去假设这个变量的值。准确地说,优化器在用到volatile修饰的变量时必须每次都小心地重新读取变量的值,而不是使用保存在寄存器里的备份。
使用volatile变量的例子:
A、并行设备的硬件寄存器(如:状态寄存器)
B、一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
C、多线程应用中被几个任务共享的变量
深入理解:
(1)一个参数既可以是const还可以是volatile吗?解释为什么。
是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
(2)一个指针可以是volatile 吗?解释为什么。
是的。尽管这并不很常见。一个例子是当一个中服务子程序修改一个指向一个buffer的指针时。
(3)下面的函数有什么错误:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
函数目的是用来返指针*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;
}
14、嵌入式系统中断服务子程序(ISR)
中断是嵌入式系统中重要的组成部分,导致了很多编译开发商提供一种扩展—让标准C支持中断。具体代表是,产生了一个新的关键字 __interrupt。下面的代码就使用了__interrupt关键字去定义了一个中断服务子程序(ISR),请评论一下这段代码的。
__interrupt double compute_area (double radius)
{
double area = PI * radius * radius;
printf("Area = %f", area);
return area;
}
中断程序的特点:
A、ISR 不能返回一个值。
B、ISR 不能传递参数。
C、在许多的处理器/编译器中,浮点一般都是不可重入的。有些处理器/编译器需要让额处的寄存器入栈,有些处理器/编译器就是不允许在ISR中做浮点运算。此外,ISR应该是短而有效率的,在ISR中做浮点运算是不明智的。
D、printf()经常有重入和性能上的问题。
15、嵌入式C语言开发中的位操作
A、将寄存器指定位(第n位)置为1
GPXX |= (1<
GPXX |= (1<< 7) | (1<< 4 ) | (1<< 0);//第0、4、7位置1,其他保留
B、将寄存器指定位(第n位)置为0
GPXX &= ~(1<
将寄存器的第n位清0,而又不影响其它位的现有状态。
GPXX &= ~(1<<4 )
C、嵌入式开发位操作实例
unsigned int i = 0x00ff1234;
//i |= (0x1<<13);//bit13置1
//i |= (0xF<<4);//bit4-bit7置1
//i &= ~(1<<17);//清除bit17
//i &= ~(0x1f<<12);//清除bit12开始的5位
//取出bit3-bit8
//i &= (0x3F<<3);//保留bit3-bit8,其他位清零
//i >>= 3;//右移3位
//给寄存器的bit7-bit17赋值937
//i &= ~(0x7FF<<7);//bit7-bit17清零
//i |= (937<<7);//bit7-bit17赋值
//将寄存器bit7-bit17的值加17
// unsigned int a = i;//将a作为i的副本,避免i的其他位被修改
// a &= (0x7FF<<7);//取出bit7-bit17
//a >>= 7;//
// a += 17;//加17
// i &= ~(0x7FF<<7);//将i的bit7-bit17清零
// i |= (a<<7);//将+17后的数写入bit7-bit17,其他位不变
//给一个寄存器的bit7-bit17赋值937,同时给bit21-bit25赋值17
i &= ~((0x7FF<<7) | (0x1F<<21));//bit7-bit17、bit21-bit25清零
i |= ((937<<7) | (17<<21));//bit7-bit17、bit21-bit25赋值
D、位操作的宏定义
//用宏定义将32位数x的第n位(bit0为第1位)置位
#define SET_BIT_N(x,n) (x | (1U<<(n-1)))
//用宏定义将32位数x的第n位(bit0为第1位)清零
#define CLEAR_BIT_N(x,n) (x & (~(1U<<(n-1))))
//用宏定义将32位数x的第n位到第m位(bit0为第1位)置位
#define SET_BITS_N_M(x,n,m) (x | (((~0U)>>(32-(m-n+1)))<<(n-1)))
//用宏定义将32位数x的第n位到第m位(bit0为第1位)清零
#define CLEAR_BITS_N_M(x,n,m) (x & (~(((~0U)>>(32-(m-n+1)))<<(n-1))))
//用宏定义获取32位数x的第n位到第m位(bit0为第1位)的部分
#define GET_BITS_N_M(x,n,m) ((x & ~(~(0U)<<(m-n+1))<<(n-1))>>(n-1))
16、大小端的介绍
Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。
数字0x12 34 56 78在内存中的表示形式为:
大端模式:
低地址 -----------------> 高地址
0x12 | 0x34 | 0x56 | 0x78
小端模式:
低地址 ------------------> 高地址
0x78 | 0x56 | 0x34 | 0x12
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) -- 低位
--------------
低地址
C语言实现测试大小端:
#include
int main(int argc, char **argv)
{
union
{
int a;
char b;
}c;
c.a = 1;
if(c.b == 1)
{
printf("little\n");
}
else
printf("big\n");
return 0;
}
常见CPU的大小端:
Big Endian : PowerPC、IBM、Sun
Little Endian : x86、DEC
常见文件的字节序:
Adobe PS – Big Endian
BMP – Little Endian
DXF(AutoCAD) – Variable
GIF – Little Endian
JPEG – Big Endian
MacPaint – Big Endian
RTF – Little Endian
Java和所有的网络通讯协议都是使用Big-Endian的编码。
大小端的转换:
16位
#define BigtoLittle16(A) (( ((uint16)(A) & 0xff00) >> 8) | \
(( (uint16)(A) & 0x00ff) << 8))
32位
#define BigtoLittle32(A) ((( (uint32)(A) & 0xff000000) >> 24) | \
(( (uint32)(A) & 0x00ff0000) >> 8) | \
(( (uint32)(A) & 0x0000ff00) << 8) | \
(( (uint32)(A) & 0x000000ff) << 24))
从软件的角度上,不同端模式的处理器进行数据传递时必须要考虑端模式的不同。如进行网络数据传递时,必须要考虑端模式的转换。在Socket接口编程中,以下几个函数用于大小端字节序的转换。
ntohs(n) //16位数据类型网络字节顺序到主机字节顺序的转换
htons(n) //16位数据类型主机字节顺序到网络字节顺序的转换
ntohl(n) //32位数据类型网络字节顺序到主机字节顺序的转换
htonl(n) //32位数据类型主机字节顺序到网络字节顺序的转换
17、引用和指针有什么区别?
A、应用必须初始化,指针不必;
B、引用处画化后不能改变,指针可以被改变;
C、不存在指向空值的引用,但存在指向空值的指针;
18、写出float,bool,int类型与零的比较,假设变量为X:
Int : if(x==0)
Float: if(x > -0.0000001 && x < 0.0000001)
Bool: if(x==false)
19、多维数组的定义
在数组定义int a[2][2]={{3},{2,3}};则a[0][1]的值为0。(对)
#include
intmain(int argc,char * argv[])
{
int a [3][2]={(0,1),(2,3),(4,5)};
int *p;
p=a [0];
printf("%d",p[0]);
}
问打印出来结果是多少?
答案:1
分析:花括号里嵌套的是小括号而不是花括号!这里是花括号里面嵌套了逗号表达式!其实这个赋值就相当于int a [3][2]={ 1, 3, 5};
20、评价下面的代码片断
unsigned int zero = 0;
unsigned int compzero = 0xFFFF;
对于一个int型不是16位的处理器为说,上面的代码是不正确的。应编写如下:
unsigned int compzero = ~0;