Thursday, January 28, 2010

常用编辑器的列模式和十六进制编辑

       今天看到有一种说法:做为一个比较强大的编辑器,起码应该具有列编辑模式和十六进制编辑功能。下面把我常用的三个通用型编辑器的以上两个功能做一个比较:
1、Source Insight
在Source Insight的帮助文档里面有如下描述:
Make Column Selection
The Make Column Selection is used with the mouse. Alt+Left Click and drag creates a rectangular selection. The column selection can be Cut, Copied, or Pasted.
按下Alt键的同时鼠标左键拖动即可进行列编辑。但是并未找到有关SI支持hex编辑的说明。 
 
2、SlickEdit
在帮助文档的BasicEditing部分有详细的描述:
 

Block selection

This is created when columns of text are selected, also known as a "column selection".

Right-click and drag or use the select_block command (Ctrl+B or Edit Select Block ). See also Block Selections.

可以使用鼠标右键拖动或者快捷键来完成块的选择。
关于hex编辑有如下描述:
You can enable Hex view/edit mode on a per-document or language-specific basis:
To view the current binary or text file in a Hex mode, click View → Hex or View → Line Hex (or use the commands hex or linehex, respectively).
To enable Hex or Line Hex view on a language-specific basis, so that each file opened in that language is displayed in Hex mode, use the Language-Specific View Options.
3、UltraEdit
在帮助文档的关于列模式有如下描述:
Column Mode command (Column menu)    
This command changes the operation of the editor for selecting text.  When column mode is selected highlighted text is based on the character column position of the first character selected to the column of the last character of text selected.  Text selected in this mode does not automatically include all text between the start and end position, but includes all text in the columns between the first and last character selected.
Column mode editing is sometimes referred to as block mode editing as the act of selecting text forms a rectangle.
 
Cut, Copy and Paste work with the selected text.
 
Column mode editing requires that Word Wrap be turned off and a fixed pitch font be selected.
 
When column mode is enabled, additional column features are enabled; Insert/Fill Columns, Delete Columns, Cut Columns and Insert Number.
 
Shortcut:        ALT+C
关于十六进制的编辑有如下描述:
Hex Edit command (Edit menu)  
The Hex Edit command selects the editing mode for the active window. The mode is toggled between Hexadecimal (HEX) and ASCII. The ASCII mode of editing is typically used for any ASCII (text) based files. In the ASCII mode UltraEdit allows normal operation of all functions and allows any characters to be typed.
 
The Hex mode of editing is typically used for non-ASCII files, or binary files. These files typically contain non-printable characters and are not text files.
       对于这三个软件我并没有明显的偏好,只是在不同的场合下选用不同的软件。比如只编辑查看几个文件或者文件是二进制文件等情况的时候,一般会选择UltraEdit.

Protel错误之net contains unplated pads

       今天一个同学在Protel DRC检查时出现了如下错误:
       [Un-Routed Net Constraint Violation] Advanced PCB Net GND Warning - net contains unplated pads
       这个错误表示net上有的焊盘没有plated(镀金)。检查网络上的焊盘,勾上plated选项。即可解决。Protel DXP下双击焊盘在左下角即可看到该选项,protel 99SE下双击焊盘,在Advanced标签下即可找到该选项。
       发生这种错误有可能是封装库焊盘的属性未选中该项,找到对应的封装批量修改即可。

Monday, January 25, 2010

typedef的四个用途和两个陷阱

 
用途一:
定义一种类型的别名,而不只是简单的宏替换。可以用作同时声明指针型的多个对象。比如:
char* pa, pb;  // 这多数不符合我们的意图,它只声明了一个指向字符变量的指针, 
// 和一个字符变量;
以下则可行:
typedef char* PCHAR;  // 一般用大写
PCHAR pa, pb;        // 可行,同时声明了两个指向字符变量的指针
虽然:
char *pa, *pb;
也可行,但相对来说没有用typedef的形式直观,尤其在需要大量指针的地方,typedef的方式更省事。

用途二:
用在旧的C代码中(具体多旧没有查),帮助struct。以前的代码中,声明struct新对象时,必须要带上struct,即形式为: struct 结构名 对象名,如:
struct tagPOINT1
{
    int x;
    int y;
};
struct tagPOINT1 p1; 

而在C++中,则可以直接写:结构名 对象名,即:
tagPOINT1 p1;

估计某人觉得经常多写一个struct太麻烦了,于是就发明了:
typedef struct tagPOINT
{
    int x;
    int y;
}POINT;

POINT p1; // 这样就比原来的方式少写了一个struct,比较省事,尤其在大量使用的时候

或许,在C++中,typedef的这种用途二不是很大,但是理解了它,对掌握以前的旧代码还是有帮助的,毕竟我们在项目中有可能会遇到较早些年代遗留下来的代码。

用途三:
用typedef来定义与平台无关的类型。
比如定义一个叫 REAL 的浮点类型,在目标平台一上,让它表示最高精度的类型为:
typedef long double REAL; 
在不支持 long double 的平台二上,改为:
typedef double REAL; 
在连 double 都不支持的平台三上,改为:
typedef float REAL; 
也就是说,当跨平台时,只要改下 typedef 本身就行,不用对其他源码做任何修改。
标准库就广泛使用了这个技巧,比如size_t。
另外,因为typedef是定义了一种类型的新别名,不是简单的字符串替换,所以它比宏来得稳健(虽然用宏有时也可以完成以上的用途)。

用途四:
为复杂的声明定义一个新的简单的别名。方法是:在原来的声明里逐步用别名替换一部分复杂声明,如此循环,把带变量名的部分留到最后替换,得到的就是原声明的最简化版。举例:

1. 原声明:int *(*a[5])(int, char*);
变量名为a,直接用一个新别名pFun替换a就可以了:
typedef int *(*pFun)(int, char*); 
原声明的最简化版:
pFun a[5]; 

2. 原声明:void (*b[10]) (void (*)());
变量名为b,先替换右边部分括号里的,pFunParam为别名一:
typedef void (*pFunParam)();
再替换左边的变量b,pFunx为别名二:
typedef void (*pFunx)(pFunParam);
原声明的最简化版:
pFunx b[10];

3. 原声明:doube(*)() (*e)[9]; 
变量名为e,先替换左边部分,pFuny为别名一:
typedef double(*pFuny)();
再替换右边的变量e,pFunParamy为别名二
typedef pFuny (*pFunParamy)[9];
原声明的最简化版:
pFunParamy e; 

理解复杂声明可用的“右左法则”:从变量名看起,先往右,再往左,碰到一个圆括号就调转阅读的方向;括号内分析完就跳出括号,还是按先右后左的顺序,如此循环,直到整个声明分析完。举例:
int (*func)(int *p);
首先找到变量名func,外面有一对圆括号,而且左边是一个*号,这说明func是一个指针;然后跳出这个圆括号,先看右边,又遇到圆括号,这说明 (*func)是一个函数,所以func是一个指向这类函数的指针,即函数指针,这类函数具有int*类型的形参,返回值类型是int。
int (*func[5])(int *);
func右边是一个[]运算符,说明func是具有5个元素的数组;func的左边有一个*,说明func的元素是指针(注意这里的*不是修饰 func,而是修饰func[5]的,原因是[]运算符优先级比*高,func先跟[]结合)。跳出这个括号,看右边,又遇到圆括号,说明func数组的元素是函数类型的指针,它指向的函数具有int*类型的形参,返回值类型为int。

也可以记住2个模式:
type (*)(....)函数指针 
type (*)[]数组指针 
---------------------------------

陷阱一:
记住,typedef是定义了一种类型的新别名,不同于宏,它不是简单的字符串替换。比如:
先定义:
typedef char* PSTR;
然后:
int mystrcmp(const PSTR, const PSTR);

const PSTR实际上相当于const char*吗?不是的,它实际上相当于char* const。
原因在于const给予了整个指针本身以常量性,也就是形成了常量指针char* const。
简单来说,记住当const和typedef一起出现时,typedef不会是简单的字符串替换就行。

陷阱二:
typedef在语法上是一个存储类的关键字(如auto、extern、mutable、static、register等一样),虽然它并不真正影响对象的存储特性,如:
typedef static int INT2; //不可行
编译将失败,会提示“指定了一个以上的存储类”。

Sunday, January 24, 2010

关于AVR单片机的JTAG和PC口复用

昨天崔说有一个ATmega32的核心板,在程序中PC口配置为输出且输出低电平的时候,在板子上实际测试还是高电平,且这写IO口输出端未接任何外设,不会受到外界电压影响。我让他置位JTD试试结果他说单纯置位JTD没效果,重新启动之后读MCUCSR寄存器的话JTD还是0。后来又修改了JTAGEN熔丝位这个问题才得到了解决。我不知道他是否按照下面的说法在四个周期内两次写操作JTD,所以无法确定是否单独修改JTD真的无法禁掉JTAG,还是必须要置位JTD和修改JTAGEN双管齐下,留待下次有板子了验证。。。
今天特地查了一下ATmega32的数据手册,总结一下JTAG和PC口复用的一些东西吧。
对于AVR系列的单片机,很多芯片都具备了JTAG接口,比如ATmega16,ATmega32,ATmega64等(除了ATmega8),而且JTAG口通常都是和PC口复用的。
在ATmega32的手册中有如下描述:若JTAG接口使能,即使出现复位,引脚PC5(TDI)、PC3(TMS) 与PC2(TCK) 的上拉电阻将被激活。通过置位MCUCSR寄存器的JTD或不对JTAG熔丝位编程可以禁止JTAG接口。
MCUCSR寄存器中的JTD位有如下说明:
此位为0时,如果JTAGEN熔丝位被编程则JTAG接口使能。如果这位为1,JTAG接口禁止。为了避免无意的禁止或使能 JTAG 接口,必须通过一个时间序列来改变JTD 位。应用软件必须在四个时钟周期内将期望的数值两次写入JTD。
此外还有两点需要注意的:
1、如果JTAG接口没有与其他JTAG电路连接,JTD应该置位。这样做的原因是为了避免JTAG接口TDO引脚的静态电流,因此可以减小系统的功耗尤其是休眠模式下的功耗。
2、必须对JTAGEN熔丝位进行编程才能使能JTAG测试访问端口。此外还必须编程熔丝位OCDEN,并保持所有的锁定位处于非锁定状态,才能真正使片上调试系统工作。

Friday, January 22, 2010

关于单片机自动复位

前几天在使用单片机的时候,改完程序后发现主函数会不断的从头开始执行。起初怀疑是复位电路出问题了。但是烧写别的程序的话不会出现这种问题,于是仔细看了一下修改过的代码。最后找到原因如下:
我在代码中开了一个中断,但是并没有添加相应的服务程序。而单片机复位上电默认会到中断向量表的起始处进行跳转然后执行,如果没有定义某个中断的ISR的话,相当于对应与这个中断的那部分是空着的。但是一旦中断触发,他还是会跳转到那个地方去查找ISR的地址,而这时候地址处并没又出现跳转到ISR的指令,于是会往下继续执行,在我的那个应用程序中接下来就执行到了主函数的地址,从结果上看起来就是单片机自动复位了。
所以,一旦开了中断,就要定义相应的中断处理程序以避免不必要的麻烦。实际上使用的时候还需要注意中断的开关、中断的嵌套等问题。

关于单片机程序的跑飞和无限循环

在单片机系统中,通常都会在程序里面加上一个无限循环,使用操作系统时其实也是类似的,比如uCOS下的每个任务的处理函数通常情况下也会是一个无限循环。至于原因,在“uC/OS-II 初级程序员指南”一文中有如下的解释:
“至于为什么要写成无限循环的形式呢?那是因为系统会为每一个任务保留一个堆栈空间,由系统在任务切换的时候换恢复上下文,并执行一条reti 指令返回。如果允许任务执行到最后一个花括号(那一般都意味着一条ret指令)的话,很可能会破坏系统堆栈空间从而使应用程序的执行不确定。换句话说,就是‘跑飞’了。所以,每一个任务必须被写成无限循环的形式。程序员一定要相信,自己的任务是会放弃CPU使用权的,而不管是系统强制(通过ISR)还是主动放弃(通过调用OS API)。”
在这段描述中,归结到“如果执行到最后很可能会破坏系统的堆栈空间从而使应用程序的执行不确定”,个人感觉这么总结不太完善。系统的堆栈空间是在RAM中的,被破坏只是有这种可能性而已。归根到底的原因是执行完之后系统可能会执行在FLASH空间里面接下来的语句,而接下来的语句是应用相关的,不可预测的。而这种不受控制的执行一般来说会破坏系统程序的执行环境,其中一种可能性就是破坏系统的堆栈空间。这样的话会导致系统的不确定行并且没有安全性可言。因此应该尽量避免这种不确定的情况的发生,对于代码程序员需要明确其执行的确定性和位置。
当然这只是程序跑飞的一个可能性,还有一些情况,比如处理器收到辐射或者在恶劣环境工作导致芯片损坏等,这些情况一般会比较少,除非能确定工作环境会造成这些情况,要不然一般还是怀疑程序的健壮性吧。另外,加上看门狗也在一定程度上能够保证程序的健壮性。

关于opaque

在yaffs2的源码里面有这么一行代码:
typedef struct __opaque yaffs_DIR;
而该头文件中并没有任何关于struct __opaque的定义,搜寻了一下系统里的其他头文件,也没有找到。google了一下才找到一篇介绍这种用法的文章Programming Tools - Opaque Pointers(http://www.qnx.com/developers/articles/article_302_2.html)。

opaque直译的意思是不透明的,C语言中允许通过typedef申明一个抽象的结构体类型,如上例所示,你无需定义struct __opaque的具体实现,就能在其他函数的声明中使用该数据类型的指针。注意,只能是指针,如果是void foo(yaffs_DIR dir),系统就会提示error: dir has incomplete type。

在库文件中,opaque类型的实现如下面的代码所示:

typedef struct{ __u32 magic;
yaffs_dirent de; /* directory entry being used by this dsc */
char name[NAME_MAX+1]; /* name of directory being searched */
yaffs_Object *dirObj; /* ptr to directory being searched */
yaffs_Object *nextReturn; /* obj to be returned by next readddir*/
int offset;
struct list_head others;
} yaffsfs_DirectorySearchContext;
yaffs_DIR *dir = NULL;
yaffsfs_DirectorySearchContext *dsc = NULL;
...
dsc = YMALLOC(sizeof(yaffsfs_DirectorySearchContext));
dir = (yaffs_DIR *)dsc;
代码中为抽象类型yaffs_DIR的指针分配了一个具体类型yaffsfs_DirectorySearchContext的空间。而这一层对使用者是不可见的,也许这是opaque这个名字的由来,这种做法可以提高库文件升级过程中对外接口的稳定性。

最后提一点,opaque并不是一个关键词,你可以任意使用其他名字,只是用opaque更能明确地表示这是一个希望对用户隐藏内部结构的数据类型。


Programming Tools - Opaque Pointers

One of the most powerful concepts when writing software is abstraction - hiding the details of a system behind a simpler interface. This article shows you how to use a language feature of C (and C++) to provide a very powerful form of abstraction for use in libraries: opaque pointers.

C/C++ has an interesting language feature. If you declare a typedef of a structure pointer, you don't need to provide that structure's definition. For example:

typedef struct _hidden_struct *handle;

This has declared a new type, handle, which is a pointer to a struct _hidden_struct. What's interesting about this? Mainly the fact that you can now use this handle type without ever having a definition of the structure it's a pointer to. Why is this powerful? That's what this article will show you (some people might already see why). Please note that the typedef above (and those used throught this article) aren't strictly needed to hide the structure's internal definition - you can just keep using the struct keyword as part of the name. However, the typedefs are used to make an abstraction away from the fact that there's a structure at all, instead of turning it into a "handle."

Sample problem

To really see the power of opaque pointers, we'll need a problem to solve. Let's suppose we want to make an image library that loads and saves bitmaps. We know that as time goes on this library will need to be able to do more things (new image types, basic transforms) and we want to preserve both compile time and runtime compatibility for this library. This means that if we have an older application, it should work with a new shared library and that if that older application is rebuilt against the new library and headers, it should still build without errors.

What about C++?

Before this goes much further, I need to address the waving hands of all the C++ fans out there, who might be thinking: "C++ already lets me do all of this behind a nice class interface with inheritance and other nice C++ language features". And this is pretty much true in the case of compile-time compatibility. But, because C++ doesn't let you separate the public and private definitions of a class, the way the class is declared changes its runtime behavior (class size, vtable offsets, etc.) - you can't provide runtime compatibility without a lot of tender care (and sometimes special compilers). So, this is still useful to C++ people, even if the slant is more towards those of us using C.

Okay, back to the library. When people design libraries like this, they'll often declare a structure that will get filled in by the library and an API for manipulating this structure. For example:

typedef struct

{

void *ptr;

int size;

int bytes_per_pixel;

} bitmap_t;

int bitmap_load_file( char *filename, bitmap_t *bitmap );

int bitmap_save_file( char *filename, bitmap_t *bitmap );

So, to use this library, you would declare a bitmap_t variable and invoke bitmap_load_file() with a filename and a pointer to the bitmap_t that was declared. For example:

bitmap_t bitmap;

int ret;

ret = bitmap_load_file( "sample.bmp", &bitmap );

Then the user of the library could simply access the pointer inside the structure and, along with the size and bytes_per_pixel members, go to work on the image. However, because the contents of bitmap_t are known publicly, if the library is updated with new entries in that structure, then the size of the structure will have been changed. And applications that use an updated shared library will probably crash or Other Bad Things when they call into the new library with a structure of a smaller (different) size.

Alternatively, the API could be defined to take a structure size:

int bitmap_load_file( char *filename, bitmap_t *bitmap, int size );

int bitmap_save_file( char *filename, bitmap_t *bitmap, int size );

and would be used like this:

ret = bitmap_load_file( "sample.bmp", &bitmap, sizeof( bitmap_t ) );

Which effectively tags the version of the library by using the size of the structure. However, this makes for a terrible support nightmare inside the library where the structure size has to be checked and different code paths are taken based on this structure size. This leads to a lot of code bloat.

We could also try to avoid the structure size issue by padding the structure with some "reserved" space:

typedef struct

{

void *ptr;

int size;

int bytes_per_pixel;

char reserved[64];

} bitmap_t;

But this is only a temporary fix. What if we didn't choose a value that's large enough? Then we're back to the case where a new library causes problems. If we're liberal with our reserved space, then we waste memory. (Since you're reading this on a QNX web page, I'm guessing that wasting memory doesn't sit well with you either.)

Another problem common to all of these approaches is what can occur if the layout of the structure changes. Say a new version of the library is built with a structure definition that looks like this:

typedef struct

{

int version;

void *ptr;

int size;

int bytes_per_pixel;

} bitmap_t;

Then the compiled apps will also get "confused", since what was previously the structure member ptr is now version, and so on. The position of a structure member within a structure is important. Seems pretty much impossible to meet our goals, huh? Fear not, loyal readers, the situation isn't that dire!

Hiding the structure's internals

The common thread to all the situations above was that the compiled application was aware of the size of the structure and the location in the structure of the structure members. So we need to hide the internals of the structure and provide access functions to get the important data out of the structure.

Let's try out a new public interface:

typedef struct _internal_bitmap * bitmap_t;

int bitmap_alloc( bitmap_t *bitmap );

int bitmap_free( bitmap_t *bitmap );

int bitmap_load_file( bitmap_t bitmap, char *filename );

int bitmap_save_file( bitmap_t bitmap, char *filename );

int bitmap_get_ptr( bitmap_t bitmap, void **ptr );

int bitmap_get_size( bitmap_t bitmap, int *size );

int bitmap_get_bpp( bitmap_t bitmap, int *bpp );

And now we can maintain a private interface that applications never get to see or use, only the library:

struct _internal_bitmap

{

void *ptr;

int size;

int bytes_per_pixel;

}

Did you notice the opaque pointer? Also, notice we've added "access functions" to get the interesting data from the new bitmap "handle"? It's pretty obvious now that we're passing in a bitmap_t (a structure pointer) as a handle to the library, but the alloc and free functions are a little confusing for people.

When we declare a bitmap_t, we're really just declaring a pointer to a structure, so we need to provide some memory for that pointer to point at. Here are the guts of the bitmap_alloc() function:

int bitmap_alloc( bitmap_t *bitmap )

{

struct _internal_bitmap *handle;

handle = ( struct _internal_bitmap * )malloc( sizeof( *handle ) );

if( handle == NULL )

{

return -1;

}

memset( handle, 0, sizeof( *handle ) );

*bitmap = handle;

return 0;

}

Since a bitmap_t is just a struct pointer, we allocate the proper sized struct (which we can do - this code is part of the library and it knows how big the structure is). Once we've verified that the malloc() didn't fail, we assign the newly allocated structure to the bitmap_t pointer. So when the application calls this function, it will get back the proper sized structure to pass into the rest of the library functions.

Here's an example of an "access function" that uses the allocated bitmap handle:

int bitmap_get_ptr( bitmap_t bitmap, void **ptr )

{

if( ptr == NULL ){ return -1; }

*ptr = bitmap->ptr;

return 0;

}

Since the library knows the definition of the _internal_bitmap structure, it can directly access its members. If you tried to access the internals of the bitmap_t handle in application code, the compiler would return an error, because it has no idea how the structure is organized or what any of its members are named.

For the last bit of code, I'll write a function that loads a bitmap, sets all the pixels to 255, and writes the bitmap back:

int turn_bitmap_white( char *filename ) {

int ret, i;

bitmap_t bitmap;

unsigned char *ptr;

int size;

ret = bitmap_alloc( &bitmap );

if( ret )

return ret;

ret = bitmap_load_file( bitmap, filename );

if( ret )

return ret;

ret = bitmap_get_ptr( bitmap, (void **)&ptr );

ret |= bitmap_get_size( bitmap, &size );

if( ret )

return ret;

for( i=0; i<size; i++ )

{

ptr[i] = 255;

}

ret = bitmap_save_file( bitmap, filename );

if( ret )

return ret;

bitmap_free( &bitmap );

return 0;

}

Problem solved

So, we've solved our problem. If we change the structure layout, we'll be okay, since the application code can't access the internals of the structure directly and must use "access functions" to get at the internal data.

If we change the size of the structure, we'll be okay, since the library itself allocates the memory for the structure and knows the proper size of the structure to allocate for the given version of the library. You can now replace the library (in shared library form) without having to rebuild any applications. And you can rebuild applications against the library without change. Now, this does assume that you haven't changed the API to your library - opaque pointers are a powerful tool, but they can't perform magic.

Lastly, the use of opaque pointers enforces good programming practice by providing a defined interface (abstraction) between the application and the library. This is usually a good method even when doing your own projects, since it lets you easily separate functionality for future projects!

本文来自CSDN博客,原文出处:http://blog.csdn.net/proing/archive/2008/12/08/3475507.aspx

Thursday, January 21, 2010

关于OS_STK的宽度

       移植μC/OS的时候,用户必须将任务堆栈的数据类型告诉给μC/OS-Ⅱ,所有的任务堆栈都必须用OS_TSK 声明数据类型。这个过程是通过为OS_STK 声明正确的C 数据类型来完成的。而这个数据类型的宽度是和处理器相关的,需要与处理器上的堆栈成员宽度一致,而处理器上的堆栈一般与处理器上的寄存器宽度或者数据宽度一致。我们只需要根据这个标准设定OS_STK的宽度即可。比如在ADS编译器上unsigned int的宽度是4个字节,而ARM处理器的堆栈宽度也为4个字节,因此可以定义OS_STK如下:
typedef unsigned int   OS_STK;                   /*Each stack entry is 32-bit wide*/

如何确定OS_STK_GROWTH

在移植uCOS的时候需要确定堆栈的增长方向,绝大多数的微处理器和微控制器的堆栈是从上往下长的。但是某些处理器是用另外一种方式工作的。μC/OS-Ⅱ被设计成两种情况都可以处理,只要在结构常量OS_STK_GROWTH中指定堆栈的生长方式就可以了。
OS_STK_GROWTH 为0 表示堆栈从下往上长。
OS_STK_GROWTH 为1 表示堆栈从上往下长。
当需要将uCOS移植到一个新的处理器上的时候需要知道堆栈的增长方向,可以通过查看处理器的架构手册来对于PUSH和POP指令的描述确定堆栈增长方向。比如,对于ARM系列处理器,在ARM Architecture Reference Manual里对PUSH指令的描述有如下一句:The end_address value is four less than the original value of SP,也就是说堆栈是从上往下增长的,入栈的时候地址减小了。
在x86平台的宏定义如下:
#define  OS_STK_GROWTH        1        /* Stack grows from HIGH to LOW memory on 80x86  */

VC中预处理指令与宏定义的妙用

今天在看源代码的时候看到在宏定义里面以##n##
方式连接字符串,起初我以为是使用的编译器特定的语法规则,查了一下应 该是C语言的标准。以下两篇CSDN的博客文章对于C语言的宏讲述的比较详细清楚,以为借鉴。
VC中预处理指令与宏定义的妙用之一
刚接触到MFC编程的人往往会被MFC 向导生成的各种宏定义和预处理指令所吓倒,但是预处理和宏定义又是C语言的一个强大工具。使 用它们可以进行简单的源代码控制,版本控制,预警或者完成一些特殊的功能。

  一个经典的例子

  使用预处理与宏定义最经典的例子莫过于加在一个头文件中以避免头文件被两次编译。试想这种的情况,有一个文件 headerfile.h 它被包含在headerfile1.h中,同时在headerfile2.h 中也被包含了,现在有一个CPP文件,implement.cpp 包含了headerfile1.h 和headerfile2.h:

#include “headerfile1.h”
#include “headerfile2.h”

  假设headerfile.h 中定义了一个全局变量 iglobal 。

int iglobal;

  在编译的时候编译器两次编译headerfile,也就会发现iglobal被定义了两次,这时就会发生变量重定义的编译 错误。

  传统的解决办法是使用#ifdef 以及#endif 来避免头文件的重复编译,在上面的例子中,只需要加上这么几行:

#ifndef smartnose_2002_6_21_headerfile_h
#define smartnose_2002_6_21_headerfile_h

int iglobal;

#endif

  仔细的考虑上面的宏定义,会发现当编译器编译过一次headerfile.h以后,smartnose_2002_6_21_headerfile_h 这个宏就被定义了,以后对headerfile.h的编译都会跳过int iglobal 这一行。当然smartnose_2002_6_21_headerfile_h 这个宏是可以任意定义的,但是这个宏本身不能和其 它文件中定义的宏重复,所以MFC在自动生成的文件中总是使用一个随机产生的长度非常长的宏,但我觉得这没有必要,我建议 在这个宏中加入一些有意义的信息,比方作者,文件名,文件创建时间等等,因为我们有时候会忘记在注释中加入这些信息。

  在VC.net 中我们不会再看见这些宏定义了,因为在这里会普遍使用一个预处理指令:

#pragma once

  只要在头文件的最开始加入这条指令就能够保证头文件被编译一次,这条指令实际上在VC6中就已经有了,但是考虑到兼容性并 没有太多的使用它。

  源代码版本控制

  当我们为许多平台开发多个版本的时候预编译指令和宏定义也能够帮我们的忙。假设我们现在为Windows 和Linux开发了一套软件,由于这两种系统的不同,我们不得不在程序控制源代码的版本。比方内存的分配,我们可以在LINUX上使 用标准C的malloc 函数,但是我们希望在 WINDOWS上使用HeapAlloc API。下面的代码演示了这种情况:

main()
{
………………..
#ifdef _WINDOWS_PLATFORM
HeapAlloc(5);
#else
malloc(5);
#endif
………………..
}

  当我们在WINDOWS 平台上编译此程序的时候,只需要定义_WINDOWS_PLATFORM这个宏,那么 HeapAlloc这条语句就能够起作用了。这样就能够让我们在同一个文件中为不同的平台实现不同版本的代码,同时保持程序的良好结 构。在许多情况下,我们还可以为一个方法使用不同的算法,然后用宏定义来针对不同的情况选择其中的一个进行编译。这在 MFC应用程序中是使用得最多的。最明显的就是文件中经常存在的

#ifdef _DEBUG

…………………….some code………..

#endif

  这样的代码,这些代码在应用程序的调试版(DEBUG)中会发挥其作用。

  #Pragma 指令

  在所有的预处理指令中,#Pragma 指令可能是最复杂的了,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。 其格式一般为

  #Pragma Para

  其中Para 为参数,下面来看一些常用的参数。

  message 参数。 Message 参数是我最喜欢的一个参数,它能够在编译信息输出窗口中输出相应的信息,这对于 源代码信息的控制是非常重要的。其使用方法为:

#Pragma message(“消息文本”)

  当编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来。

  当我们在程序中定义了许多宏来控制源代码版本的时候,我们自己有可能都会忘记有没有正确的设置这些宏,此时我们可以用这条 指令在编译的时候就进行检查。假设我们希望判断自己有没有在源代码的什么地方定义了_X86这个宏可以用下面的方法

#ifdef _X86

#Pragma message(“_X86 macro activated!”)

#endif

  当我们定义了_X86这个宏以后,应用程序在编译时就会在编译输出窗口里显示“_X86 macro activated!”。我 们就不会因为不记得自己定义的一些特定的宏而抓耳挠腮了。

  另一个使用得比较多的pragma参数是code_seg。格式如:

#pragma code_seg( ["section-name"[,"section-class"] ] )

  它能够设置程序中函数代码存放的代码段,当我们开发驱动程序的时候就会使用到它。

  最后一个比较常用的就是上面所说的#pragma once 指令了。

  VC预定义的宏

  在VC中有一类宏并不是由用户用#define语句定义的,而是编译器本身就能够识别它们。这些宏的作用也是相当大的。让 我们来看第一个,也是MFC中使用得最频繁的一个:__FILE__ 。

  当编译器遇到这个宏时就把它展开成当前被编译文件的文件名。好了,我们马上就可以想到可以用它来做什么,当应用程序发生错 误时,我们可以报告这个错误发生的程序代码在哪个文件里,比方在文件test.cpp中有这样的代码:

try
{
char * p=new(char[10]);
}
catch(CException *e )
{
TRACE(“ there is an error in file: %s\n”,__FILE__);
}

  在程序运行的时候,如果内存分配出现了错误,那么在调试窗口中会出现there is an error in file: test.cpp 这句话,当然,我们还可以把这个错误信息显示在别的地方。

  如果我们还能够记录错误发生在哪一行就好了,幸运的是,与__FILE__宏定义一样,还有一个宏记录了当前代码所在的行数,这 个宏是__LINE__。使用上面的两个宏,我们可以写出一个类似于VC提供的ASSERT语句。下面是方法

#define MyAssert(x) \
if(!(x)) \
MessageBox(__FILE__,__LINE__,NULL,MB_OK);

  我们在应用程序中可以象使用ASSERT语句一样使用它,在错误发生时,它会弹出一个对话框,其标题和内容告诉了我们错误 发生的文件和代码行号,方便我们的调试,这对于不能使用ASSERT语句的项目来说是非常有用的。

  除了这两个宏以外,还有记录编译时间的__TIME__,记录日期的__DATE__,以及记录文件修改时间的__TIMESTAMP__ 宏。

  使用这些预定义的宏,我们几乎可以生成和VC能够生成的一样完整的源代码信息报表。

  结论

  翻开MFC和Linux的源代码,宏定义几乎占据了半边天,消息映射,队列操作,平台移植,版本管理,甚至内核模块的拆卸 安装都用宏定义完成。毫不夸张的说,有些文件甚至就只能看见宏定义。所以学习宏定义,熟练的使用宏定义对于学习C语言乃至VC都是非 常关键的。


本文来自CSDN博客,原文地址:http://blog.csdn.net/jiu23/archive/2007/08/27/1760670.aspx
VC中预处理指令与宏定义的妙用之二
在上一篇文章中,我演示了几个常用的宏定义和预处理指令,但可以说这些都是相当常规的技巧。下面要介绍的宏定义与预处理指 令的用法也是ATL,MFC以及Linux中使用得比较多的非常重要的技巧。
  ## 连接符与# 符
  ## 连接符号由两个井号组成,其功能是在带参数的宏定义中将两个子串(token)联接起来,从而形成一个新的子 串。但它不可以是第一个或者最后一个子串。所谓的子串(token)就是指编译器能够识别的最小语法单元。具体的定义在编译原理里有 详尽的解释,但不知道也无所谓。同时值得注意的是#符是把传递过来的参数当成字符串进行替代。下面来看看它们是怎样工作的。这是 MSDN上的一个例子。
  假设程序中已经定义了这样一个带参数的宏:
#define paster( n ) printf( "token" #n " = %d", token##n )
  同时又定义了一个整形变量:
int token9 = 9;
  现在在主程序中以下面的方式调用这个宏:
paster( 9 );
  那么在编译时,上面的这句话被扩展为:
printf( "token" "9" " = %d", token9 );
  注意到在这个例子中,paster(9);中的这个”9”被原封不动的当成了一个字符串,与”token”连接在了一起,从 而成为了token9。而#n也被”9”所替代。
  可想而知,上面程序运行的结果就是在屏幕上打印出token9=9
  在ATL的编程中,我们查看它的源代码就会经常看见这样的一段:
#define IMPLEMENTS_INTERFACE(Itf) \
{&IID_##Itf, ENTRY_IS_OFFSET,BASE_OFFSET(_ITCls, Itf) },
  我们经常不假思索的这样使用它:
……
IMPLEMENTS_INTERFACE(ICat)
……
  实际上IID_ICat 已经在别的地方由ATL向导定义了。当没有向导的时候,你只要遵循把IID_加在你的接口名 前面来定义GUID的规则就也可以使用这个宏。在实际的开发过程中可能很少用到这种技巧,但是ATL使用得如此广泛,而其中又出现了 不少这样的源代码,所以明白它是怎么一回事也是相当重要的。我的一个朋友就是因为不知道IMPLEMENTS_INTERFACE 宏是怎么定义的,而又不小心改动了IID_ICat的定义而忙活了一整天。
  Linux的怪圈
  在刚开始阅读Linux的时候有一个小小的宏让我百思不得其解:
#define wait_event(wq,condition) \
do{ \
if(condition) \
break; \
__wait_event(wq,condition); \
}while(0)
  这是一个奇怪的循环,它根本就只会运行一次,为什么不去掉外面的do{..}while结构呢?我曾一度在心里把它叫 做“怪圈”。原来这也是非常巧妙的技巧。在工程中可能经常会引起麻烦,而上面的定义能够保证这些麻烦不会出现。下面是解释:
  假设有这样一个宏定义
#define macro(condition) \
if(condition) dosomething();
  现在在程序中这样使用这个宏:
if(temp)
macro(i);
else
doanotherthing();
  一切看起来很正常,但是仔细想想。这个宏会展开成:
if(temp)
if(condition) dosomething();
else
doanotherthing();
  这时的else不是与第一个if语句匹配,而是错误的与第二个if语句进行了匹配,编译通过了,但是运行的结果一定是 错误的。
  为了避免这个错误,我们使用do{….}while(0) 把它包裹起来,成为一个独立的语法单元,从而不会与上下文发生混淆。同 时因为绝大多数的编译器都能够识别do{…}while(0)这种无用的循环并进行优化,所以使用这种方法也不会导致程序的性能降 低。
  几个小小的警告
  正如微软声称的一样,宏定义与预编译器指令是强大的,但是它又使得程序难以调试。所以在定义宏的时候不要节省你的字符 串,一定要力争完整的描述这个宏的功能。同时在定义宏的时候如有必要(比方使用了if语句)就要使用 do{…}while(0)将它封闭起来。在宏定义的时候一定要注意各个宏之间的相互依赖关系,尽量避免这种依赖关系的存在。下面就 有这样一个例子。
  设有一个静态数组组成的整型队列,在定义中使用了这样的方法: int array[]={5, 6, 7, 8};
  我们还需要在程序中遍历这个数组。通常的做法是使用一个宏定义
#define ELE_NUM 4
…………………………..
……………………………..
for(int I=0;I<
  由于某种偶然的原因,我们删除了定义中的一个元素,使它变成:
array[]={5,6,7}
  而却忘了修改ELE_NUM的值。那么在上面的代码中马上就会发生访问异常,程序崩溃。然后是彻夜不眠的调试,最后发 现问题出在这个宏定义上。解决这个问题的方法是不使用
array[]={….}这样的定义,而显式的申明数组的大小:
array[ELE_NUM]={….}
  这样在改动数组定义的时候,我们就不会不记得去改宏定义了。总之,就是在使用宏定义的时候能够用宏定义的地方统统都用上。
  我发现的另一个有趣的现象是这样的:
  假设现在有一个课程管理系统,学生的人数用宏定义为:
#define STU_NUM 50
  而老师的人数恰好也是50人,于是很多人把所有涉及到老师人数的地方通通用上STU_NUM这个宏。另一个学期过去, 学生中的一个被开除了,系统需要改变。怎么办呢?简单的使用#define STU_NUM 49 么?如果是这样,一个老师也就被开除了,我 们不得不手工在程序中去找那些STU_NUM宏然后判断它是否是表示学生的数目,如果是,就把它改成49。天哪,这个宏定 义制造的麻烦比使用它带来的方便还多。正确的方法应该是为老师的数目另外定义一个宏:
#define TEA_NUM 50
  当学生的数目改变以后只要把STU_NUM 定义为49就完成了系统的更改。所以,当程序中的两个量之间没有必然联系的时候一定 不要用其中的一个宏去替代另一个,那只会让你的程序根本无法改动。
  最后,建议C/C++语言的初学者尽可能多的在你的程序中使用宏定义和预编译指令。多看看MFC,ATL或者 LINUX的源代码,你会发现C语言强大的原因所在。
本文来自CSDN博客,原文地址:http://blog.csdn.net/jiu23/archive/2007/08/28/1761462.aspx

ADS1.2 C语言基本数据类型宽度定义

在一些场合下需要知道确切数据类型宽度,比如移植操作系统时。ADS是ARM工程构建比较常用的一个IDE,今天需要了解ADS1.2编译器的C语言基本数据类型宽度在官方文档里面的是如何定义的,于是找了一下貌似没有明确的写数据类型宽度的文字,只是在Compilers and Libraries Guide文档(DUI 0067D)里面的C and C++ Compiler Implementation Limits部分定义了D.3 Limits for integral numbers(Page261)和D.4 Limits for floating-point numbers(Page262),从这两部分定义的各个数据类型的最大最小值就可以看出各种基本数据类型的宽度了。在实际使用中可定义如下:
/*
*********************************************************************************************************
*                                              DATA TYPES
*                                         (Compiler Specific)
*********************************************************************************************************
*/

typedef unsigned char  BOOLEAN;
typedef unsigned char  INT8U;                    /* Unsigned  8 bit quantity                 */
typedef signed   char  INT8S;                    /* Signed    8 bit quantity                    */
typedef unsigned short   INT16U;                   /* Unsigned 16 bit quantity              */
typedef signed   short   INT16S;                   /* Signed   16 bit quantity                 */
typedef unsigned long  INT32U;                   /* Unsigned 32 bit quantity                */
typedef signed   long  INT32S;                   /* Signed   32 bit quantity                   */
typedef float          FP32;                     /* Single precision floating point                */
typedef double         FP64;                     /* Double precision floating point            */