嵌入式面试题总结一

https://max.book118.com/html/2017/0227/93769054.shtm

/************************************1**********************************************/
int a[4]={1,2,3,4};  // a:数组首元素的首地址,即 a[0];    &a:数组的首地址  
int *ptr=(int*)(&a+1); // a+1:数组的下一元素的首地址,即 a[1]; 
                       // &a+1:下一数组的首地址,即(int)&a+4*sizeof(int) ,ptr 等效于  &a[4] 
printf("%d",*(ptr-1));//ptr-1 就  等效于   &a[3]  ,加上* 取内容,那么结果就是 4

/*********************************2**************************************************/
int main()
{
    if(-1L > 1UL)
        printf("1\n");
    else
        printf("0\n");
    return 0;
}
/*
常量后面接L表示long型存储,U表示unsigned,F表示float
此题的关键是 -1L > 1UL
一个是long型,一个是unsigned long型,无符号和有符号的比较,那么编译器会把有符号的转换为无符号。
-1L = 0xFFFFFFFF
1UL = 0x00000001
因为 0xFFFFFFFF > 0x00000001 ,所以 -1L > 1UL 
运行结果为:打印1
*/
/*********************************3*************************************************/
int main()
{
    if(-1 > 1)
        printf("1\n");
    else
        printf("0\n");
    return 0;
}
/*
-1和1都没声明存储类型,编译器默认按int型来存储。
int型 -1 小于 1,因此if条件不成立,执行else里的语句。

运行结果为:打印0
*/
/*******************************4**********************************************/
int main()
{
    struct test
    {
        char f1 : 3;
        short f2 : 4;
        char f3 : 5;
    }; 
printf("%d\n",sizeof(test));

}
/*
位段定义,省内存,可以用来定义寄存器,灵活操作哪一位
https://blog.csdn.net/m0_37655357/article/details/79707202
http://blog.sina.com.cn/s/blog_4f4fb35f01000e0w.html

含位域结构体 
使用位域的主要目的是压缩存储,其大致规则为:  
1)   如果相邻位域字段的类型相同,且其位宽之和小于类型的 sizeof 大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;  
2)   如果相邻位域字段的类型相同,但其位宽之和大于类型的 sizeof 大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;  
3)   如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6 采取不压缩方式,Dev-C++采取压缩方式;  
4)   如果位域字段之间穿插着非位域字段,则不进行压缩;  
5)   整个结构体的总大小为最宽基本类型成员大小的整数倍。
1.  struct   test    
2.  {    
3.      char   f1   :   3;    
4.      short  f2   :   4; //位域类型为 char,第 1 个字节仅能容纳下 f1 和 f2,所以 f2 被压缩到第 1 个字节中 
5.      char   f3   :   5; //f3 只能从下一个字节开始 
6.  };           //sizeof(test)=2

*/
/***********************************5******************************************/
int main()
{
    int x=10;
    x+=3+x%(-3),
    printf("%d\n",x);
}
/*
x+=3+x%(-3)
首先是 求得 x%(-3) 的值 ,x = 10 ,是正数,因此 %(-3 )后 结果为1
求余运算,除数和被除数都可以是负的,求余后的值 与 被除数 的符号相同,比如 -10 % -3 = -1, 10 % -3 = 1. 
之后就是优先级问题
x += 3 + 1 ,即 x = x + (3 + 1) = 14
*/
/***********************************6*****************************************/
typedef static char int8;这个声明正确吗?
http://www.cnblogs.com/yangguang-it/p/6926018.html

/***********************************7***********************************/

#include <stdio.h> 
int main()
{
    char a[20] = {1, 2, 3, 4, 5, 6,7,8,9,10,11,12,13,14,15,16,17,18,19};
    int *ptr = (int *)a;
    int *ptr2 =(int *)(&a+1);
    printf("0x%X, 0x%X \n",  *(ptr + 2), *(ptr2 - 2));
}
/*
结果:0xC0B0A09, 0x100F0E0D 
&a+1:下一数组的首地址,即&[20];
还有就是大小端模式:
大端模式:是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:
地址由小向大增加,而数据从高位往低位放;这和我们的阅读习惯一致。
小端模式:是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,
高地址部分权值高,低地址部分权值低。
测试本机模式:
#include<stdio.h>
int main(int argc, char *argv[])
{
    int i = 0x12345678;
    char c = i;

    printf("%x \n", c);

    return 0;
}
如果它打印出78(小端模式),如果打印出12(大端模式);
*/
/************************************8********************************************************/
const int a;
int const a;
const int *a;
int * const a;
int const * a const;
/*
1、a是一个常整型数
2、a是一个常整型数
3、a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以)
4、a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)
5、a是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不可修改的)
*/
/************************************9********************************************************/
int a,b;
a=b=1;
b=a++,b++,++a;
//求b的结果是多少?
/*
逗号 的优先级是最低的,因此,本题的结果就是 先执行 b=a++ ,此时 b 为 1 。 然后 b++ ,所以 b 的值 最终为 2
*/
/*************************************10*******************************************************/
//写一个"标准"宏MIN ,这个宏输入两个参数并返回较小的一个。
/*
define MIN(A,B) ( (A) <= (B) ? (A) : (B) )[/code]
思考:
1、标识符#define在宏中应用的基本知识。这是很重要的,因为直到嵌入(inline)操作符变为标准C的一部分,宏是方便产生嵌入代码的唯一方法,对于嵌入式系统来说,为了能达到要求的性能,嵌入代码经常是必须的方法。
2、三重条件操作符的知识。这个操作符存在C语言中的原因是它使得编译器能产生比if-then-else更优化的代码,了解这个用法是很重要的。
3、懂得在宏中小心地把参数用括号括起来(要养成这样的好习惯)
4、我也用这个问题开始讨论宏的副作用,例如:当你写下面的代码时会发生什么事?least = MIN(*p++, b);
*/
/****************************************11****************************************************/
//数据声明(Data declarations) 的应用
题目:用变量a给出下面的定义
1、一个整型数(An integer) 
2、一个指向整型数的指针( A pointer to an integer)
3、一个指向指针的的指针,它指向的指针是指向一个整数( A pointer to a pointer to an intege)
4、一个有10个整型数的数组( An array of 10 integers)
5、一个有10个指针的数组,该指针是指向一个整型数的。(An array of 10 pointers to integers) 
6、 一个指向有10个整型数数组的指针( A pointer to an array of 10 integers) 
7、 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a function that takes an integer as an argument and returns an integer) 
8、一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数( An array of ten pointers to functions that take an integer argument and return an integer )
/*
1、 int a; 
2、 int *a; 
3、 int **a; 
4、 int a[10]; 
5、 int *a[10]; 等价于int *(a[10]);
6、 int (*a)[10]; 
7、 int (*max_function)(int a); 
8、 int (*a[10])(int); 
*/
/***************************************12*****************************************************/
//如何输出源文件的文件名和当前执行的行数
/*
printf(”The file name: %d\n”, __FILE__);
printf(”The current line No:%d\n”, __LINE__);
C语言 有 几个 宏定义是 大家需要掌握的:
__FILE__ 包含当前程序文件名的字符串
__LINE__  表示当前行号的整数
__DATE__ 包含当前日期的字符串
__STDC__  如果编译器遵循ANSI C标准,它就是个非零值
__TIME__ 包含当前时间的字符串

这题目考的就是 __FILE__  和 __LINE__  。(注意,是两边各两个下划线)
*/
/**************************************13******************************************************/
//volatile是干啥用的,(必须将cpu的寄存器缓存机制回答的很透彻),使用实例有哪些?(重点)

1)访问寄存器比访问内存单元要快,编译器会优化减少内存的读取,可能会读脏数据。声明变量为volatile,编译器不再对访问该变量的代码优化,仍然从内存读取,使访问稳定。

总结:volatile关键词影响编译器编译的结果,用volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不再编译优化,以免出错。

2)使用实例如下(区分C程序员和嵌入式系统程序员的最基本的问题。):

并行设备的硬件寄存器(如:状态寄存器)

一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)

多线程应用中被几个任务共享的变量

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

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

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

可以。尽管这并不很常见。一个例子当中断服务子程序修该一个指向一个buffer的指针时。

下面的函数有什么错误:
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************************************/
static const等等的用法,(能说出越多越好)(重点)

首先说说const的用法(绝对不能说是常数)

1)在定义的时候必须进行初始化

2)指针可以是const 指针,也可以是指向const对象的指针

3)定义为const的形参,即在函数内部是不能被修改的

4)类的成员函数可以被声明为常成员函数,不能修改类的成员变量

5)类的成员函数可以返回的是常对象,即被const声明的对象

6)类的成员变量是常成员变量不能在声明时初始化,必须在构造函数的列表里进行初始化

(注:千万不要说const是个常数,会被认为是外行人的!!!!哪怕说个只读也行)

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

const int a; a是一个常整型数

int const a; a是一个常整型数

const int *a; a是一个指向常整型数的指针,整型数是不可修改的,但指针可以

int * const a; a为指向整型数的常指针,指针指向的整型数可以修改,但指针是不可修改的

int const * a const; a是一个指向常整型数的常指针,指针指向的整型数是不可修改的,同时指针也是不可修改的

通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。

Const如何做到只读?

这些在编译期间完成,对于内置类型,如int, 编译器可能使用常数直接替换掉对此变量的引用。而对于结构体不一定。

再说说static的用法(三个明显的作用一定要答出来)

1)在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。

2)在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。

3)在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用

4)类内的static成员变量属于整个类所拥有,不能在类内进行定义,只能在类的作用域内进行定义

5)类内的static成员函数属于整个类所拥有,不能包含this指针,只能调用static成员函数

static全局变量与普通的全局变量有什么区别?static局部变量和普通局部变量有什么区别?static函数与普通函数有什么区别?

static全局变量与普通的全局变量有什么区别:static全局变量只初使化一次,防止在其他文件单元中被引用;

static局部变量和普通局部变量有什么区别:static局部变量只被初始化一次,下一次依据上一次结果值;

static函数与普通函数有什么区别:static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝

/*********************************15*************************************/
extern c 作用

告诉编译器该段代码以C语言进行编译。

5.指针和引用的区别

1)引用是直接访问,指针是间接访问。

2)引用是变量的别名,本身不单独分配自己的内存空间,而指针有自己的内存空间

3)引用绑定内存空间(必须赋初值),是一个变量别名不能更改绑定,可以改变对象的值。

总的来说:引用既具有指针的效率,又具有变量使用的方便性和直观性

/********************************16**************************************/
关于静态内存分配和动态内存分配的区别及过程

1) 静态内存分配是在编译时完成的,不占用CPU资源;动态分配内存运行时完成,分配与释放需要占用CPU资源;

2)静态内存分配是在栈上分配的,动态内存是堆上分配的;

3)动态内存分配需要指针或引用数据类型的支持,而静态内存分配不需要;

4)静态内存分配是按计划分配,在编译前确定内存块的大小,动态内存分配运行时按需分配。

5)静态分配内存是把内存的控制权交给了编译器,动态内存把内存的控制权交给了程序员;

6)静态分配内存的运行效率要比动态分配内存的效率要高,因为动态内存分配与释放需要额外的开销;动态内存管理水平严重依赖于程序员的水平,处理不当容易造成内存泄漏。

/*********************************17*************************************/
头文件中的 ifndef/define/endif 干什么用?

预处理,防止头文件被重复使用,包括pragma once都是这样的

/*********************************18*************************************/
宏定义求两个元素的最小值

#define MIN(A,B) ((A) <= (B) ? (A) : (B))

/**********************************19************************************/
分别设置和清除一个整数的第三位?

#define BIT3 (0x1<<3)

static int a;

void set_bit3(void){

a |= BIT3;

}

void clear_bit3(void){

a &= ~BIT3;

}

/*********************************20*************************************/
用预处理指令#define 声明一个常数,用以表明1年中有多少秒

#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL

/*********************************21*************************************/
预处理器标识#error的目的是什么?

抛出错误提示,标识外部宏是否被定义!

/*********************************22*************************************/
嵌入式系统中经常要用到无限循环,你怎么样用C编写死循环呢?

记住这是第一方案!!!!

while(1)

{

}

一些程序员更喜欢如下方案:

for(;;){

}

汇编语言的无线循环是:

Loop:

...

goto Loop;

/*********************************23*************************************/
用变量a给出下面的定义

一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数 int (*a[10])(int);
/**********************************24************************************/
中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展—让标准C支持中断。具代表事实是,产生了一个新的关键字 __interrupt

/**********************************25************************************/
枚举与#define 宏的区别
1)#define 宏常量是在预编译阶段进行简单替换。枚举常量则是在编译的时候确定其值。

2)可以调试枚举常量,但是不能调试宏常量。

3)枚举可以一次定义大量相关的常量,而#define 宏一次只能定义一个
/**********************************26*****************************************/
内存对齐的原则?
A.结构体的大小为最大成员的整数倍。

B.成员首地址的偏移量为其类型大小整数倍。

/******************************27*********************************************/
深入谈谈堆和栈

1).分配和管理方式不同 :

堆是动态分配的,其空间的分配和释放都由程序员控制。

栈由编译器自动管理。栈有两种分配方式:静态分配和动态分配。静态分配由编译器完成,比如局部变量的分配。动态分配由alloca()函数进行分配,但是栈的动态分配和堆是不同的,它的动态分配是由编译器进行释放,无须手工控制。

2).产生碎片不同

对堆来说,频繁的new/delete或者malloc/free势必会造成内存空间的不连续,造成大量的碎片,使程序效率降低。

对栈而言,则不存在碎片问题,因为栈是先进后出的队列,永远不可能有一个内存块从栈中间弹出。

3).生长方向不同

堆是向着内存地址增加的方向增长的,从内存的低地址向高地址方向增长。

栈是向着内存地址减小的方向增长,由内存的高地址向低地址方向增长。
/******************************28*********************************************/
内存的静态分配和动态分配的区别?

时间不同。静态分配发生在程序编译和连接时。动态分配则发生在程序调入和执行时。

空间不同。堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。
静态分配是编译器完成的,比如局部变量的分配。alloca,可以从栈里动态分配内存,不用担心内存泄露问题,
当函数返回时,通过alloca申请的内存就会被自动释放掉。

/******************************29*********************************************/
写一个函数,将字符串翻转,翻转方式如下:“I am a student”反转成“student a am I”,不借助任何库函数
#include "stdio.h"
#include <iostream>

void revesal(char * start, char* end)
{

    char *temp_s = start;
    char *temp_e = end;
    while(temp_s < temp_e)
    {
        char temp= *temp_s;
        *temp_s= *temp_e;
        *temp_e = temp;
        ++temp_s;
        --temp_e;
    }
    return;

}

/******************************30*********************************************/
int main(void)
{
    char i = -1;
    unsigned short t = i;
    printf("%d",t);
}
/*
这题目考的是类型转换
i 是 int8 类型的,赋值为 -1;
把此值给 uint16 类型的 变量 t ,结果就等于 (uint16)(-1) = 0xFFFF,即65535;
*/
/******************************31*********************************************/
__I、 __O 、__IO是什么意思?
这是ST库里面的宏定义,定义如下:
#define     __I       volatile const        /*!< defines 'read only' permissions      */
#define     __O     volatile                  /*!< defines 'write only' permissions     */
#define     __IO    volatile                  /*!< defines 'read / write' permissions   */


显然,这三个宏定义都是用来替换成 volatile 和 const 的,所以我们先要了解 这两个关键字的作用:

volatile
简单的说,就是不让编译器进行优化,即每次读取或者修改值的时候,都必须重新从内存或者寄存器中读取或者修改。

    一般说来,volatile用在如下的几个地方:
    1、中断服务程序中修改的供其它程序检测的变量需要加volatile;
    2、多任务环境下各任务间共享的标志应该加volatile;
    3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;

我认为这是区分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;   
}   

const
    只读变量,即变量保存在只读静态存储区。编译时,如何尝试修改只读变量,则编译器提示出错,就能防止误修改。
    const与define
   两者都可以用来定义常量,但是const定义时,定义了常量的类型,所以更精确一些(其实const定义的是只读变量,而不是常量)。
   #define只是简单的文本替换,除了可以定义常量外,还可以用来定义一些简单的函数,有点类似内置函数。const和define定义的常
   量可以放在头文件里面。(小注:可以多次声明,但只能定义一次)

    const与指针    
int me;   
const int * p1=&me;           //p1可变,*p1不可变             const 修饰的是 *p1,即*p1不可变
int * const p2=&me;           //p2不可变,*p2可变             const 修饰的是 p2,即p2不可变
const int *const p3=&me;   //p3不可变,*p3也不可变       前一个const 修饰的是 *p3,后一个const 修饰的是p3,两者都不可变

前面介绍了 volatile 和 const 的用法,不知道大家了解了没?了解了后,下面的讲解就更加容易了:
__I :输入口。既然是输入,那么寄存器的值就随时会外部修改,那就不能进行优化,每次都要重新从寄存器中读取。也不能写,即只读,不然就不是输入而是输出了。
__O :输出口,也不能进行优化,不然你连续两次输出相同值,编译器认为没改变,就忽略了后面那一次输出,假如外部在两次输出中间修改了值,那就影响输出
__IO:输入输出口,同上

为什么加下划线?

原因是:避免命名冲突
一般宏定义都是大写,但因为这里的字母比较少,所以再添加下划线来区分。这样一般都可以避免命名冲突问题,因为很少人这样命名,这样命名的人肯定知道这些是有什么用的。
经常写大工程时,都会发现老是命名冲突,要不是全局变量冲突,要不就是宏定义冲突,所以我们要尽量避免这些问题,不然出问题了都不知道问题在哪里。

/******************************32*********************************************/
宏定义的用法

注意:宏定义不是函数!!

一般用来简化操作的,但又能避免函数调用那样需要进行切换环境,花费时间。例如:
#define max (a,b) (a>b?a:b)
#define MALLOC(n, type)   ((type *)  malloc( (n) * sizeof (type) ))

使用时,我只需:
a=max (a,b);                 //而不是a=(a>b?a:b);
int *p=MALLOC(10,int);  //而不是int *p= ((int *)  malloc( (10) * sizeof (int) ))

网上copy一篇不知出自哪里的文章:
1、防止一个头文件被重复包含 
#ifndef COMDEF_H 
#define COMDEF_H  //头文件内容
#endif 

2、重新定义一些类型,防止由于各种平台和编译器的不同,而产生的类型字节数差异,方便移植。 
typedef  unsigned char      boolean;     /* Boolean value type. */ 
  
typedef  unsigned long int  uint32;      /* Unsigned 32 bit value */ 
typedef  unsigned short     uint16;      /* Unsigned 16 bit value */ 
typedef  unsigned char      uint8;       /* Unsigned 8  bit value */ 
  
typedef  signed long int    int32;       /* Signed 32 bit value */ 
typedef  signed short       int16;       /* Signed 16 bit value */ 
typedef  signed char        int8;        /* Signed 8  bit value */ 

3、得到指定地址上的一个字节或字 
#define  MEM_B( x )  ( *( (byte *) (x) ) ) 
#define  MEM_W( x )  ( *( (word *) (x) ) ) 

4、求最大值和最小值 
   #define  MAX( x, y ) ( ((x) > (y)) ? (x) : (y) ) 
   #define  MIN( x, y ) ( ((x) < (y)) ? (x) : (y) ) 

5、得到一个field在结构体(struct)中的偏移量 
#define FPOS( type, field )   ( (dword) &(( type *) 0)-> field ) 

6、得到一个结构体中field所占用的字节数 
#define FSIZ( type, field ) sizeof( ((type *) 0)->field ) 

7、按照LSB格式把两个字节转化为一个Word 
#define  FLIPW( ray ) ( (((word) (ray)[0]) * 256) + (ray)[1] ) 

8、按照LSB格式把一个Word转化为两个字节 
#define  FLOPW( ray, val ) \ 
  (ray)[0] = ((val) / 256); \ 
  (ray)[1] = ((val) & 0xFF) 

9、得到一个变量的地址(word宽度) 
#define  B_PTR( var )  ( (byte *) (void *) &(var) ) 
#define  W_PTR( var )  ( (word *) (void *) &(var) ) 

10、得到一个字的高位和低位字节 
#define  WORD_LO(xxx)  ((byte) ((word)(xxx) & 255)) 
#define  WORD_HI(xxx)  ((byte) ((word)(xxx) >> 8)) 

11、返回一个比X大的最接近的8的倍数 
#define RND8( x )       ((((x) + 7) / 8 ) * 8 ) 

12、将一个字母转换为大写 
#define  UPCASE( c ) ( ((c) >= 'a' && (c) <= 'z') ? ((c) - 0x20) : (c) ) 

13、判断字符是不是10进值的数字 
#define  DECCHK( c ) ((c) >= '0' && (c) <= '9') 

14、判断字符是不是16进值的数字 
#define  HEXCHK( c ) ( ((c) >= '0' && (c) <= '9') ||\ 
                       ((c) >= 'A' && (c) <= 'F') ||\ 
((c) >= 'a' && (c) <= 'f') ) 

15、防止溢出的一个方法 
#define  INC_SAT( val )  (val = ((val)+1 > (val)) ? (val)+1 : (val)) 

16、返回数组元素的个数 
#define  ARR_SIZE( a )  ( sizeof( (a) ) / sizeof( (a[0]) ) ) 

17、返回一个无符号数n尾的值MOD_BY_POWER_OF_TWO(X,n)=X%(2^n) 
#define MOD_BY_POWER_OF_TWO( val, mod_by ) \ 
           ( (dword)(val) & (dword)((mod_by)-1) ) 

18、对于IO空间映射在存储空间的结构,输入输出处理 
  #define inp(port)         (*((volatile byte *) (port))) 
  #define inpw(port)        (*((volatile word *) (port))) 
  #define inpdw(port)       (*((volatile dword *)(port))) 
   
  #define outp(port, val)   (*((volatile byte *) (port)) = ((byte) (val))) 
  #define outpw(port, val)  (*((volatile word *) (port)) = ((word) (val))) 
  #define outpdw(port, val) (*((volatile dword *) (port)) = ((dword) (val))) 

19、使用一些宏跟踪调试 
A N S I标准说明了五个预定义的宏名。它们是: 
_ L I N E _ 
_ F I L E _ 
_ D A T E _ 
_ T I M E _ 
_ S T D C _ 
如果编译不是标准的,则可能仅支持以上宏名中的几个,或根本不支持。记住编译程序 
也许还提供其它预定义的宏名。 
_ L I N E _及_ F I L E _宏指令在有关# l i n e的部分中已讨论,这里讨论其余的宏名。 
_ D AT E _宏指令含有形式为月/日/年的串,表示源文件被翻译到代码时的日期。 
源代码翻译到目标代码的时间作为串包含在_ T I M E _中。串形式为时:分:秒。 
如果实现是标准的,则宏_ S T D C _含有十进制常量1。如果它含有任何其它数,则实现是 
非标准的。 
可以定义宏,例如: 
当定义了_DEBUG,输出数据信息和所在文件所在行 
#ifdef _DEBUG 
#define DEBUGMSG(msg,date) printf(msg);printf(“%d%d%d”,date,_LINE_,_FILE_) 
#else 
      #define DEBUGMSG(msg,date)  
#endif 

20、宏定义防止 使用是错误 
用小括号包含。 
例如:#define ADD(a,b) (a+b) 
用do{}while(0)语句包含多语句防止错误 
例如:#difne DO(a,b) a+b;\ 
                   a++; 
应用时:if(….) 
                    DO(a,b); //产生错误 
            else 
                   ……
解决方法: #difne DO(a,b) do{a+b;\ 
                   a++;}while(0)

猜你喜欢

转载自blog.csdn.net/zdw6868/article/details/83015470
今日推荐