嵌入式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;//如果一个数是2N次方,返回0

else

return 1;//如果一个数不是2N次方,返回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)//047位置1,其他保留

    B、将寄存器指定位(第n位置为0

    GPXX &= ~1<

    将寄存器的n位清0,而又不影响其它位的现有状态。

    GPXX &= ~1<<4 )

    C、嵌入式开发位操作实例

    unsigned int i = 0x00ff1234;

    //i |= (0x1<<13);//bit131

    //i |= (0xF<<4);//bit4-bit71

    //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);//ibit7-bit17清零

   // i |= (a<<7);//+17后的数写入bit7-bit17,其他位不变

 

    //给一个寄存器的bit7-bit17赋值937,同时给bit21-bit25赋值17

    i &= ~((0x7FF<<7) | (0x1F<<21));//bit7-bit17bit21-bit25清零

    i |= ((937<<7) | (17<<21));//bit7-bit17bit21-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、写出floatboolint类型与零的比较,假设变量为X

       Int     ifx==0

       Float    ifx > -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;