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.youkuaiyun.com/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)