C语言__attribute__和宏

一 介绍

  GNU C的一大特色就是 __attribute__ 机制。可以设置函数属性(Function Attribute)变量属性(Variable Attribute)类型属性(Type Attribute)
  关键字 __attribute__ 也可以对结构体(struct)或共用体(union)进行属性设置。大致有六个参数值可以被设定,即:alignedpackedtransparent_unionunuseddeprecatedmay_alias

二 语法

  __attribute__ 语法格式为:__attribute__((attribute-list))
  在使用 __attribute__ 参数时,你也可以在参数的前后都加上 __ (两个下划线),例如,使用 __aligned__ 而不是 aligned,这样,你就可以在相应的头文件里使用它而不用关心头文件里是否有重名的宏定义。

三 函数属性

  函数属性可以帮助开发者把一些特性添加到函数声明中,从而可以使编译器在错误检查方面的功能更强大。__attribute__ 机制也很容易同非GNU应用程序做到兼容之功效。GNU CC需要使用–Wall编译器来击活该功能,这是控制警告信息的一个很好的方式。

1 format

  作用:__attribute__ 属性可以给被声明的函数加上类似printf或者scanf的特征,它可以使编译器检查函数声明和函数实际调用参数之间的格式化字符串是否匹配。该功能十分有用,尤其是处理一些很难发现的bug。
  语法:format(archetype, string-index, first-to-check)
  format属性告诉编译器,按照printfscanfstrftimestrfmon 的参数表格式规则对该函数的参数进行检查。archetype 指定是哪种风格;string-index 指定传入函数的第几个参数是格式化字符串;first-to-check 指定从函数的第几个参数开始按上述规则进行检查。
  具体使用:__attribute__((format(printf,m,n)))__attribute__((format(scanf,m,n)))
  其中参数m与n的含义为:m:第几个参数为格式化字符串(format string);n:参数集合中的第一个,即参数“…”里的第一个参数在函数参数总数排在第几,注意,有时函数参数里还有“隐身”的。

//m=1;n=2
extern void myprint(const char *format,...) __attribute__((format(printf,1,2)));
//m=2;n=3
extern void myprint(int l,const char *format,...) __attribute__((format(printf,2,3)));
//需要特别注意的是,如果myprint是一个函数的成员函数,那么m和n的值可有点“悬乎”了,例如:
//m=3;n=4
extern void myprint(int l,const char *format,...) __attribute__((format(printf,3,4)));
//其原因是,类成员函数的第一个参数实际上一个“隐身”的“this”指针。
//format-myprintf.c
#include <stdio.h>
#include <stdarg.h>

#ifdef ON
	//告诉编译器以printf的方式去检查该函数
	extern void myprint(const char *format,...) __attribute__((format(printf,1,2)));
#else
	extern void myprint(const char *format,...);
#endif

void test()
{
    myprint("i=%d\n",6);
    myprint("i=%s\n",6);
    myprint("i=%s\n","abc");
    myprint("%s,%d,%d\n",1,2);
}

//gcc -Wall format-myprintf.c -D ON
//gcc -Wall format-myprintf.c 
int main()
{
    test();
    return 0;
}

  注意:默认情况下,编译器是能识别类似printf的“标准”库函数。

2 noreturn

  该属性通知编译器函数从不返回值,当遇到类似函数需要返回值而却不可能运行到返回值处就已经退出来的情况,该属性可以避免出现错误信息。C库函数中的 abort()exit() 的声明格式就采用了这种格式,如下所示:
extern void exit(int) __attribute__((noreturn));
extern void abort(void) __attribute__((noreturn));

//noreturn.c
#include <stdio.h>
#include <stdarg.h>

#ifdef ON
    extern void myexit() __attribute__((noreturn));
#else
    extern void myexit() ;
#endif

int test(int n)
{
    if ( n > 0 )
    {
        myexit();
        /* 程序不可能到达这里*/
    }
    else
        return 0;
}
//https://gcc.gnu.org/onlinedocs/gcc-4.0.0/gcc/Function-Attributes.html
//__attribute__ 告诉编译器做额外的事情。

//编译对比
//gcc -Wall noreturn.c -D ON
//gcc -Wall noreturn.c
int main()
{
    test(3);
    return 0;
}

3 const

  该属性只能用于带有数值类型参数的函数上。当重复调用带有数值参数的函数时,由于返回值是相同的,所以此时编译器可以进行优化处理,除第一次需要运算外,其它只需要返回第一次的结果就可以了,进而可以提高效率。该属性主要适用于没有静态状态(static state)和副作用的一些函数,并且返回值仅仅依赖输入的参数。

//const.c
#include <stdio.h>
#include <stdarg.h>

#ifdef ON
    extern int square(int n) __attribute__((const));
#else
    extern int square(int n);
#endif
//编译对比
//gcc -Wall const.c -D ON
//gcc -Wall const.c
int main()
{
    int total=0;
    int i=0;
    for (i = 0; i < 100; i++ )
    {
            total += square(5) + i;
    }
    printf("total=%d\n",total);
    return 0;
}

4 noinline

5 always_inline

6 pure

7 nothrow

8 sentinel

9 format_arg

10 no_instrument_function

11 section

12 constructor

13 destructor

14 used

15 unused

16 deprecated

17 weak

18 malloc

a19 lias

20 warn_unused_result

21 nonnull

四 类型属性(Type Attributes)

1 aligned

//aligned.c
#include <stdio.h>
#include <stdarg.h>

#ifdef ON
//关键字__attribute__也可以对结构体(struct)或共用体(union)进行属性设置。大致有六个参数值可以被设定,
//即:aligned, packed, transparent_union, unused, deprecated 和 may_alias。
struct p
{
    int a;
    char b;
    char c;
}__attribute__((aligned(4))) pp;
struct q
{
    int a;
    char b;
    struct p qn;
    char c;
}__attribute__((aligned(8))) qq; 
#else
struct p
{
    int a;
    char b;
    char c;
} pp;

struct q
{
    int a;
    char b;
    struct p qn;
    char c;
} qq;
#endif

//__attribute__修饰变量属性(Variable Attribute)
struct test
{
    char a;
    int x[2] __attribute__ ((packed));
};

//gcc -Wall aligned.c -D ON -o a2
//gcc -Wall aligned.c -o a1
int main()
{
	printf("sizeof(int)=%d,sizeof(short)=%d.sizeof(char)=%d\n",sizeof(int),sizeof(short),sizeof(char));
    printf("pp=%d,qq=%d \n", sizeof(pp),sizeof(qq));
    return 0;
}

2 packed

3 transparent_union

4 deprecated

5 may_alias

6 constructor_destructor

//constructor_destructor.c
#include <stdio.h>
#include <stdarg.h>

#ifdef ON
static void start(void) __attribute__ ((constructor));
static void stop(void) __attribute__ ((destructor));
#else
static void start(void);
static void stop(void);
#endif
void start(void)
{
    printf("hello world!\n");
}
void stop(void)
{
    printf("goodbye world!\n");
}
//编译运行对比
//gcc -Wall noreturn.c -D ON
//gcc -Wall noreturn.c
int main(int argc, char *argv[])
{
    printf("start == %p\n", start);
    printf("stop == %p\n", stop);
    return 0;
}

五 变量属性(Variable Attribute)

六 Clang特有的

  • availability
  • overloadable

七 demo

#include <stdio.h>
#include <stdarg.h>
#ifdef ON
extern "C" int pthread_create (pthread_t*,
                const pthread_attr_t*,
                void* (*)(void*),
                void*) __attribute__ ((weak));
extern "C" int pthread_mutex_init (pthread_mutex_t*,
                    const pthread_mutexattr_t*) __attribute__ ((weak));
extern "C" int pthread_mutex_lock (pthread_mutex_t*) __attribute__ ((weak));
extern "C" int pthread_mutex_unlock (pthread_mutex_t*) __attribute__ ((weak));
extern "C" int pthread_mutex_destroy (pthread_mutex_t*) __attribute__ ((weak));
#else
extern "C" int pthread_create (pthread_t*,
                const pthread_attr_t*,
                void* (*)(void*),
                void*);
extern "C" int pthread_mutex_init (pthread_mutex_t*,
                    const pthread_mutexattr_t*);
extern "C" int pthread_mutex_lock (pthread_mutex_t*);
extern "C" int pthread_mutex_unlock (pthread_mutex_t*);
extern "C" int pthread_mutex_destroy (pthread_mutex_t*);
#endif
#define __init         __attribute__ ((__section__ (".text.init")))
#define __exit          __attribute__ ((unused, __section__(".text.exit")))
#define __initdata      __attribute__ ((__section__ (".data.init")))
#define __exitdata      __attribute__ ((unused, __section__ (".data.exit")))
#define __initsetup     __attribute__ ((unused,__section__ (".setup.init")))
#define __init_call     __attribute__ ((unused,__section__ (".initcall.init")))
#define __exit_call     __attribute__ ((unused,__section__ (".exitcall.exit")))
extern void foobar (void) __attribute__ ((section ("bar")));
void add(int a,int b) __attribute__ ((visibility ("protected")));
void sub(int a,int b) __attribute__ ((visibility ("hidden")));
//关键字__attribute__ 也可以对结构体(struct )或共用体(union )进行属性设置。大致有六个参数值可以被设定,
//即:aligned, packed, transparent_union, unused, deprecated 和 may_alias 。
struct STudent{
    int age;
    char name[10] __attribute__ ((aligned(8)));
};
//https://gcc.gnu.org/onlinedocs/gcc-4.0.0/gcc/Function-Attributes.html#Function-Attributes
int old_fn () __attribute__ ((deprecated));
void f () __attribute__ ((weak, alias ("__f")));
void mul() __attribute__ ((dllexport));
void mul2() __attribute__ ((dllimport));
extern char * my_dgettext (char *my_domain, const char *my_format)  __attribute__ ((format_arg (2)));
void f2 () __attribute__ ((interrupt ("IRQ")));
//Use naked attribute on the ARM, AVR, C4x and IP2K ports to indicate that the specified function does not need prologue/
//epilogue sequences generated by the compiler.It is up to the programmer to provide these sequences.
void mydiv() __attribute__ ((naked)); 
void mydiv2() __attribute__ ((noinline));

__attribute__ ((noinline,__annotate__(("nofcf"))))
__attribute__ ((noinline,__annotate__(("nocxf"))))
__attribute__ ((noinline,__annotate__(("nosac")))) void demo();

//编译对比
//gcc -Wall weak.c -D ON
//gcc -Wall weak.c
int main()
{
    return 0;
}

https://blog.csdn.net/weixin_33770878/article/details/92129273
  ANSI标准定义的C语言预处理程序包括下列命令:#define#error#include#if#else#elif#endif#ifdef#ifndef#undef#line#pragma等。非常明显,所有预处理命令均以符号#开头。

一 宏定义

1 #操作符

  #把一个符号直接转换为字符串,例如:

#define STRING(x) #x
const char *str = STRING( test_string );

  str的内容就是"test_string",也就是说#会把其后的符号直接加上双引号。

2 ##操作符

  1、这个运算符把两个语言符号组合成单个语言符号。例如:#define XNAME(n) x##n这样宏调用:XNAME(4)展开后为x4

#include <stdio.h>

#define XNAME(n) x##n
#define PXN(n) printf("x"#n" = %d\n",x##n)

int main(void)
{
    int XNAME(1)=12;//int x1=12;
    PXN(1);//printf("x1 = %d\n", x1);
    return 0;
}

  2、##符号的作用是在可变参数的个数为0时,消除参数前面的逗号:
  #define MY_PRINTF(fs, …) printf(fs, ##__VA_ARGS__)我们这样调用:MY_PRINTF(“Hello, World”);等价于printf(“Hello, World”);

3 可变参数宏

  可变参数宏是C语言与C++语言的函数宏的参数个数可以是0个或多个。1999年在C语言标准的ISO/IEC 9899:1999(C99)修订版和2011年ISO/IEC 14882:2011(C ++ 11)C ++语言标准修订版中引入了可变参数宏。 在C ++ 20中添加了没有参数的可变参数宏。实现思想就是宏定义中参数列表的最后一个参数为: … \ldots 。这样预定义宏__VA_ARGS__就可以被用在替换部分中,以表示省略号代表什么。比如:

#define PR(...) printf(__VA_ARGS__)`

PR("hello");//-->printf("hello");
PR("weight = %d, shipping = $.2f",wt,sp);
//-->printf("weight = %d, shipping = $.2f",wt,sp);

省略号只能代替最后面的宏参数。例如:#define W(x,...,y)错误!
  GCC 始终支持复杂的宏,它使用一种不同的语法从而可以使你可以给可变参数一个名字,如同其它参数一样。例如下面的例子:

#define debug(format, args...) fprintf (stderr, format, args)

  这和上面举的那个 ISO C 定义的宏例子是完全一样的,但是这么写可读性更强并且更容易进行描述。
  GNU CPP 还有两种更复杂的宏扩展,支持上面两种格式的定义格式。
  在标准 C 里,你不能省略可变参数,但是你却可以给它传递一个空的参数。例如,下面的宏调用在 ISO C 里是非法的,因为字符串后面没有逗号:

debug ("A message")

  GNU CPP 在这种情况下可以让你完全的忽略可变参数。在上面的例子中,编译器仍然会有问题( complain ),因为宏展开后,里面的字符串后面会有个多余的逗号。为了解决这个问题, CPP 使用一个特殊的‘ ## ’操作。书写格式为:

#define debug(format, ...) fprintf (stderr, format, ## __VA_ARGS__)

  这里,如果可变参数被忽略或为空,##操作将使预处理器(preprocessor)去除掉它前面的那个逗号。如果你在宏调用时,确实提供了一些可变参数,GNU CPP也会工作正常,它会把这些可变参数放到逗号的后面。象其它的pasted macro参数一样,这些参数不是宏的扩展。

4 #ifdef和#endif的作用

  一般情况下,源程序中所有的行都参加编译。但是有时希望对其中一部分内容只在满足一定条件才进行编译,也就是对一部分内容指定编译的条件,这就是“条件编译”。有时,希望当满足某条件时对一组语句进行编译,而当条件不满足时则编译另一组语句。
  条件编译命令最常见的形式为:

#ifdef 标识符
程序段1
#else
程序段2
#endif

  它的作用是:当标识符已经被定义过(一般是用#define命令定义),则对程序段1进行编译,否则编译程序段2。
  其中#else部分也可以没有,即:

#ifdef 程序段1
#denif

5 #ifndef和#endif的用法

#ifndef x //if not define的简写
#define x
程序段1 //如果x没有被宏定义过,定义x,并编译程序段1
#endif
程序段2 //如果x已经定义过了则编译程序段2的语句,“忽视”程序段1。

  这是宏定义的一种,它可以根据是否已经定义了一个变量来进行分支选择,一般用于调试等等。实际上确切的说这应该是预处理功能中三种(宏定义,文件包含和条件编译)中的一种----条件编译。

6 #ifdef和#if defined的区别

  #ifdef#if defined的区别在于,后者可以组成复杂的预编译条件,比如:

#if defined(AAA) && defined(BBB)
xxxxxxxxx
#endif

#if defined(AAA) || VERSION>12
xxxxxxxxx
#endif

  而#ifdef就不能用上面的用法,也就是说,当你要判断单个宏是否定义时#ifdef#if defined效果是一样的,但是当你要判断复杂的条件时,只能用#if defined

7 空宏定义

#define DBG(...)

int main()
{
	DBG("hello woold");
}
//预处理后
int main()
{
	;
}

8 自己调用自己

  当一个宏自己调用自己时,例如:#define TEST( x ) ( x + TEST( x ) ),TEST( 1 ) 会发生什么?为了防止无限制递归展开,语法规定,当一个宏遇到自己时,就停止展开,也就是说,当对TEST( 1 )进行展开时,展开过程中又发现了一个TEST,那么就将这个TEST当作一般的符号。TEST(1)最终被展开为:1 + TEST(1)

9 宏参数的prescan

  当一个宏参数被放进宏体时,这个宏参数会首先被全部展开(有例外,见下文)。当展开后的宏参数被放进宏体时,预处理器对新展开的宏体进行第二次扫描,并继续展开。例如:

#define PARAM( x ) x
#define ADDPARAM( x ) INT_##x
PARAM( ADDPARAM( 1 ) );

  因为ADDPARAM( 1 ) 是作为PARAM的宏参数,所以先将ADDPARAM( 1 )展开为INT_1,然后再将INT_1放进PARAM。
  例外情况是,如果PARAM宏里对宏参数使用了#或##,那么宏参数不会被展开

#define PARAM( x ) #x
#define ADDPARAM( x ) INT_##x
PARAM( ADDPARAM( 1 ) ); 

  将被展开为"ADDPARAM( 1 )"。
  使用这么一个规则,可以创建一个很有趣的技术:打印出一个宏被展开后的样子,这样可以方便你分析代码:

#define TO_STRING( x ) TO_STRING1( x )
#define TO_STRING1( x ) #x

  TO_STRING首先会将x全部展开(如果x也是一个宏的话),然后再传给TO_STRING1转换为字符串,现在你可以这样:const char *str = TO_STRING( PARAM( ADDPARAM( 1 ) ) );去一探PARAM展开后的样子。

10 #undef

  命令#undef取消其后那个前面已定义过有宏名定义。一般形式为:#undef macroname

11 #line

  命令#line改变__LINE____FILE__的内容,它们是在编译程序中预先定义的标识符。命令的基本形式如下:#line number["filename"],其中的数字为任何正整数,可选的文件名为任意有效文件标识符。行号为源程序中当前行号,文件名为源文件的名字。命令# line主要用于调试及其它特殊应用。
  注意:在#line后面的数字标识从下一行开始的数字标识。

二 gcc -D选项

  C语言源程序中有#define定义的宏,可以起到替换、条件编译的功能;定义宏的方式是放在头文件或者C文件中。gcc提供了另外一种宏定义的方法。假设程序需要很多宏,不可能这些宏都在编译器中定义,可以说比较重要的宏才会放在gcc的D选项后边。
  -Dname 定义宏name,默认定义内容为字符串“1”
  -Dname=defn 定义宏name,并且内容为defn

#include<stdio.h>  
int main()
{
    #ifdef HELLO
    printf("HELLO defined !\n");
    printf("HELLO = %d\n",HELLO); 
   #else
   printf("HELLO not define!\n");
   #endif 
   return 0;
}

  #gcc main.c -o main结果 :HELLO not define!
  #gcc -D HELLO main.c -o main结果 :HELLO defined!HELLO = 1
  #gcc -D HELLO=36 main.c -o main结果 :HELLO defined!HELLO = 36
  与#define的关系:

-D macro=string,等价于在头文件中定义:#define macro string
-D macro,等价于在头文件中定义:#define macro 1,实际上也达到了定义:#define macro的目的

三 应用

1 利用##定义结构体类型

#define STRUCT(type) typedef struct tag_##type type;\
struct tag_##type

STRUCT(Student)
{
	char *name;
	int id;
}

2 函数

#define KEY_IRQHANDLER(number, key) \
void Key##number##_IRQHandler(void) \
{\
	KEY_IRQHandler(key);\
}

KEY_IRQHANDLER(1, &KEY1)
KEY_IRQHANDLER(2, &KEY2)
KEY_IRQHANDLER(3, &KEY3)
KEY_IRQHANDLER(4, &KEY4)
KEY_IRQHANDLER(5, &KEY5)
KEY_IRQHANDLER(6, &KEY6)

3 打印调试信息

  ANSI C标准中有几个标准预定义宏(也是常用的):
  __LINE__:在源代码中插入当前源代码行号;
  __FILE__:在源文件中插入当前源文件名;
  __DATE__:在源文件中插入当前的编译日期;
  __TIME__:在源文件中插入当前编译时间;
  __STDC__:当要求程序严格遵循ANSI C标准时该标识被赋值为1;
  __cplusplus__:当编写C++程序时该标识符被定义。
  通过宏定义进行串口打印:

#define __DEBUG__
#ifdef __DEBUG__
#define DEBUG(format,...) printf("%s(%d)-<%s>: "format, __FILE__, __LINE__, __FUNCTION__, ##__VA_ARGS__)
#else
#define DEBUG(format,...)
#endif

四 #pragma

  在所有的预处理指令中,#pragma 指令可能是最复杂的了,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。#pragma指令对每个编译器给出了一个方法,在保持与C和C++语言完全兼容的情况下,给出主机或操作系统专有的特征。依据定义,编译指示是机器或操作系统专有的,且对于每个编译器都是不同的。

1 #pragma GCC system_header

  本节内容来自这儿
  在看公司公共库的头文件中发现了:#pragma GCC system_header一行,以前没有见过这种用法,在网上查了一下,解释如下:
  从#pragma GCC system_header直到文件结束之间的代码会被编译器视为系统头文件之中的代码。系统头文件中的代码往往不能完全遵循C标准, 所以头文件之中的警告信息往往不显示。(除非用 #warning显式指明)。
  可以查看gcc参考手册:http://gcc.gnu.org/onlinedocs/cpp/System-Headers.html#System-Headers
  The header files declaring interfaces to the operating system and runtime libraries often cannot be written in strictly conforming C. Therefore, GCC gives code found in system headersspecial treatment. All warnings, other than those generated by ‘#warning’ (see Diagnostics), are suppressed while GCC is processing a system header. Macros defined in a system header are immune to a few warnings wherever they are expanded. This immunity is granted on an ad-hoc basis, when we find that a warning generates lots of false positives because of code in macros defined in system headers.
  Normally, only the headers found in specific directories are considered system headers. These directories are determined when GCC is compiled. There are, however, two ways to make normal headers into system headers:

  • Header files found in directories added to the search path with the -isystem and -idirafter command-line options are treated as system headers for the purposes of diagnostics.
  • There is also a directive, #pragma GCC system_header, which tells GCC to consider the rest of the current include file a system header, no matter where it was found. Code that comes before the ‘#pragma’ in the file is not affected. #pragma GCC system_header has no effect in the primary source file.

2 #pragma message

  它能够在编译信息输出窗口中输出相应的信息,这对于源代码信息的控制是非常重要的。
  其使用方法为:#pragma message("消息文本")
  当编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来。
  当我们在程序中定义了许多宏来控制源代码版本的时候,我们自己有可能都会忘记有没有正确的设置这些宏,此时我们可以用这条指令在编译的时候就进行检查。假设我们希望判断自己有没有在源代码的什么地方定义了_X86这个宏可以用下面的方法:

#ifdef _X86
#pragma message("_X86 macro activated!")
#endif

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

3 #pragma code_seg

  另一个使用得比较多的#pragma参数是code_seg。格式如:#pragma code_seg( [ [ { push | pop}, ] [ identifier, ] ] [ "segment-name"[,"segment-class"]),该指令用来指定函数在.obj文件中存放的节,观察OBJ文件可以使用VC自带的dumpbin命令行程序,函数在.obj文件中默认的存放节 为.text节 。
如果code_seg没有带参数的话,则函数存放在.text节中
push (可选参数) 将一个记录放到内部编译器的堆栈中,可选参数可以为一个标识符或者节名
pop(可选参数) 将一个记录从堆栈顶端弹出,该记录可以为一个标识符或者节名
identifier (可选参数) 当使用push指令时,为压入堆栈的记录指派的一个标识符,当该标识符被删除的时候和其相关的堆栈中的记录将被弹出堆栈
“segment-name” (可选参数) 表示函数存放的节名
例如:
//默认情况下,函数被存放在.text节中
void func1() { // stored in .text
}

//将函数存放在.my_data1节中
#pragma code_seg(“.my_data1”)
void func2() { // stored in my_data1
}

//r1为标识符,将函数放入.my_data2节中
#pragma code_seg(push, r1, “.my_data2”)
void func3() { // stored in my_data2
}
int main() {
}

4 #pragma once

#ifndef
#else
#endif
这是一个比较常用的指令,只要在头文件的最开始加入这条指令就能够保证头文件被编译一次

5 #pragma hdrstop

表示预编译头文件到此为止,后面的头文件不进行预编译。
BCB可以预编译头文件以加快链接的速度,但如果所有头文件都进行预编译又可能占太多磁盘空间,所以使用这个选项排除一些头文件。
有时单元之间有依赖关系,比如单元A依赖单元B,所以单元B要先于单元A编译。你可以用#pragma startup指定编译优先级,
如果使用了#pragma package(smart_init) ,BCB就会根据优先级的大小先后编译。

6 #pragma warning

该指令允许有选择性的修改编译器的警告消息的行为
指令格式如下:
#pragma warning( warning-specifier : warning-number-list [; warning-specifier : warning-number-list…]
#pragma warning( push[ ,n ] )
#pragma warning( pop )
主要用到的警告表示有如下几个:
once:只显示一次(警告/错误等)消息
default:重置编译器的警告行为到默认状态
1,2,3,4:四个警告级别
disable:禁止指定的警告信息
error:将指定的警告信息作为错误报告
如果大家对上面的解释不是很理解,可以参考一下下面的例子及说明
#pragma warning( disable : 4507 34; once : 4385; error : 164 )
等价于:
#pragma warning(disable:4507 34) // 不显示4507和34号警告信息
#pragma warning(once:4385) // 4385号警告信息仅报告一次
#pragma warning(error:164) // 把164号警告信息作为一个错误。
同时这个pragma warning 也支持如下格式:
#pragma warning( push [ ,n ] )
#pragma warning( pop )
这里n代表一个警告等级(1—4)。
#pragma warning( push )保存所有警告信息的现有的警告状态。
#pragma warning( push, n)保存所有警告信息的现有的警告状态,并且把全局警告
等级设定为n。
#pragma warning( pop )向栈中弹出最后一个警告信息,在入栈和出栈之间所作的
一切改动取消。例如:
#pragma warning( push )
#pragma warning( disable : 4705 )
#pragma warning( disable : 4706 )
#pragma warning( disable : 4707 )
#pragma warning( pop )

在这段代码的最后,重新保存所有的警告信息(包括4705,4706和4707)

在使用标准C++进行编程的时候经常会得到很多的警告信息,而这些警告信息都是不必要的提示,
所以我们可以使用#pragma warning(disable:4786)来禁止该类型的警告

在vc中使用ADO的时候也会得到不必要的警告信息,这个时候我们可以通过
#pragma warning(disable:4146)来消除该类型的警告信息

7 #pragma comment(…)

  该指令的格式为 :#pragma comment("comment-type" [, commentstring])
  该指令将一个注释记录放入一个对象文件或可执行文件中,comment-type(注释类型):可以指定为五种预定义的标识符的其中一种。
  五种预定义的标识符为:
  compiler:将编译器的版本号和名称放入目标文件中,本条注释记录将被编译器忽略。如果你为该记录类型提供了commentstring参数,编译器将会产生一个警告。
  例如:#pragma comment(compiler)
  exestr:将commentstring参数放入目标文件中,在链接的时候这个字符串将被放入到可执行文件中,当操作系统加载可执行文件的时候,该参数字符串不会被加载到内存中。但是,该字符串可以被dumpbin之类的程序查找出并打印出来,你可以用这个标识符将版本号码之类的信息嵌入到可执行文件中。
  lib:这是一个非常常用的关键字,用来将一个库文件链接到目标文件中
常用的lib关键字,可以帮我们连入一个库文件。
  例如:#pragma comment(lib, "user32.lib"),该指令用来将user32.lib库文件加入到本工程中。
  linker:将一个链接选项放入目标文件中,你可以使用这个指令来代替由命令行传入的或者在开发环境中设置的链接选项,你可以指定/include选项来强制包含某个对象,例如: #pragma comment(linker, "/include:__mySymbol")
  可以在程序中设置下列链接选项:/DEFAULTLIB/EXPORT/INCLUDE/MERGE/SECTION
  user:将一般的注释信息放入目标文件中commentstring参数包含注释的文本信息,这个注释记录将被链接器忽略。例如:#pragma comment( user, "Compiled on " __DATE__ " at " __TIME__ )

8 #progma pack(n)

  指定结构体对齐方式!#pragma pack(n)来设定变量以n字节对齐方式。n字节对齐就是说变量存放的起始地址的偏移量有两种情况:第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式,第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。结构的总大小也有个约束条件,分下面两种情况:如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;否则必须为n的倍数。下面举例说明其用法。

#pragma pack(push) //保存对齐状态 
#pragma pack(4)//设定为4字节对齐 
struct test{ 
	char m1; 
	double m4; 
	int m3; 
}; 
#pragma pack(pop)//恢复对齐状态 

  为测试该功能,可以使用sizeof()测试结构体的长度!

五 #error

  处理器命令#error强迫编译程序停止编译,主要用于程序调试。

六 宏的妙用

1 用新函数替换原有函数

  对于一个设计好的函数,假设它已经在一个很大的工程中到处使用,突然发现它的一个不足,想修改它的功能。也许这个新增加的功能需要一个额外的参数,但是又不想修改使用这些函数的地方。假设有两个函数必须成对使用,一个占用资源并使用,另外一个则释放资源以供其他模块使用。典型的例子是,函数一(假设为Lock)获得一个全局的锁,这个锁用于保护在多线程情况下多个线程对一个公共资源如一个全局变量的访问。问题是,这个Lock函数获得锁以后,其他线程将不能再获得这个锁,直到当前线程释放这个锁。编制Lock函数的程序员同时提供了一个Unlock函数用于释放锁,并要求使用Lock的人必须对应的使用Unlock。调试程序时,发现线程被死锁,怀疑有人使用完Lock后忘记调用Unlock,但是Lock和Unlock在这个大工程中都被广泛的使用,因此设计者希望Lock和Unlock都增加两个额外的参数file和line,以说明这两个函数在哪里被调用了,哪些地方被死锁以及哪些地方调用了Lock但是没有调用Unlock。假设这两个函数的原型为:void Lock(); void Unlock();
  新设计的函数的原型是:void Lock(LPCTSTR szFileName,UINT uLineNo); void Unlock(LPCTSTR szFileName,UINT uLineNo);
  设计完新的函数后,项目经理希望所有模块统一使用这两个函数并提供文件名和行号信息作为参数。这样将是一个非常浩大且烦琐的工作,意味着重复性的劳动、数小时无聊的加班和工期的延误,这是谁都不愿意遇到的。 使用宏可以非常轻松的解决这一切。首先,应该把新设计的函数换个名字,不妨叫它们NewLock和NewUnlock,也就是他们的原型为:void NewLock(LPCTSTR szFileName,UINT uLineNo); void NewUnlock(LPCTSTR szFileName,UINT uLineNo);
  这个函数原型应该放在一个头文件中,避免在多个地方重复的声明。需要用到这两个函数的cpp文件,只要包含他们原型所在的头文件即可。为了不改动使用Lock/Unlock函数的模块,在头文件中增加如下两行:#define Lock() NewLock(__FILE__,__LINE__) #define Unlock() NewUnlock(__FILE,__LINE__)
  这样,当不同模块使用这个函数时,宏替换功能在编译时起作用,自动使用了__FILE____LINE__为参数,调用了新设计的函数。调试的时候就可以根据日志来判断什么地方遗漏了调用Unlock。

2 给一个函数捆绑其他功能

  上述方法修改了原来函数的设计。实际上,这两个函数本身没有问题,只是使用者使用上出了问题。你可能只需要在调试版本中测试到底谁遗漏了这些重要信息。对于一些严谨的公司,一旦软件被修改,推出销售前就需要进行严格的测试。因此项目经理可能不会允许修改原有函数的设计,要求直接捆绑一个测试代码。产品发售时,删除捆绑代码即可。 使用宏也可以捆绑代码,这需要首先了解一个宏的特点:如果你的代码中出现了一个字符串,编译器会首先匹配宏,并试图用宏展开。这样,即使你有同名的函数,它也不会被当作函数处理。但是,如果一个宏展开时发现,展开式是一个嵌套的宏展开,展开式就试图在进入下一次嵌套展开之前,试图用函数匹配来终止这种无限循环。 为此,定义如下两个宏:

#define Lock() Lock(); TRACE("Lock called in file = %s at line =%u\n",__FILE__,__LINE__)
#define Unlock() Unlock(); TRACE("Unlock called in file = %s at line =%u\n",__FILE__,__LINE__)

  编译器在编译过程中,发现如下代码:

//here the Lock function is called
Lock();

  它首先把这个Lock理解成宏函数,展开成:

//here the Lock function is called
Lock();
TRACE("Lock called in file = %s at line = %u\n",__FILE__,__LINE__);

  上述代码中,__FILE__和__LINE__应该同时被展开,由于与论题无关,所以还是原样给出。展开以后,Lock还是一个和宏匹配的式子,但是编译器发现如果这样下去,它将是一个无休止的迭代,因此它停止展开过程,讯中同名的函数,因此上面的代码已经是最终展开式。 这样,我们成功的不改变Lock函数的原型和设计,捆绑了一条调试信息上去。由于TRACE语句在Release版本中不会出现,这样也避免了不得不进行额外的测试过程。

3 实现一些自动化过程

  程序中需要输入一组参数,为此设计了一个对话框来输入。问题是:每次显示对话框时,都希望能按照上次输入的值显示。设计当然没有问题,在文档中保存输入的参数,在显示对话框前在把保存的值赋值给对话框对应控制变量。下面是常见的代码:

     CMyDoc * pDoc = GetDocument();
    ASSERT_VALID(pDoc);
    
    CParameterDlg dlg;
    //设置对话框初值
    dlg.m_nValue1   = pDoc->m_nValue1;
    dlg.m_szValue2  = pDoc->m_szValue2;
    ......
    dlg.m_lValuen   = pDoc->m_lValuen;
    //显示对话框
    if(dlg.DoModal() == IDOK)
    {
       //点击OK按钮后保存设置
        pDoc->m_nValue1  = dlg.m_nValue1;
        pDoc->m_szValue2 = dlg.m_szValue2;
            ......
        pDoc->m_lValuen  = dlg.m_lValuen;
    }

如果整个程序只有一两个这样的代码段,并且每个代码段涉及的变量个数都很少,当然没有问题,但是当你程序中有成百上千个这样的参数对话框,每个对话框又对应数十个这样的参数,工作量就非常可观了(而且是没有任何成就感的工作量)。我想,用VC做界面的朋友们大多遇到过这样的问题。可以注意到,上述代码在DoModal前后都是一组赋值过程,但是赋值的方向不是很一致,因此每个变量对都需要写两个赋值语句。那么是否可以做一个函数,前后各调用一次,根据一个参数决定方向。而且函数中也只需要对每个变量写一次?
下面这个函数就是一个实现:

void DataExchange(CMyDoc * pMyDoc,CParameterDlg * pDlg,BOOL flag )
{
    BEGIN_EXCHANGE(pMyDoc,CMyDoc,pDlg,CParameterDlg,flag)
    EXCHANGE(m_nValue1);
    EXCHANGE(m_szValue2);
            ....
    EXCHANGE(m_lValue2);
    END_EXCHANGE()
}

为了使上述语义能起作用,定义上面三个宏如下:

#define BEGIN_EXCHANGE(left,lefttype,right,righttype,flag) \
        {\
            CSmartPtr<lefttype> pLeft   = left;\
            CSmartPtr<righttype> pRight = right
            
#define END_EXCHANGE() }
 #define EXCHANGE(varible) \
     if(flag)\
     {\
         pLeft->varible = pRight->varible ;\
     }else{\
         pRight->varible = pLeft->varible;\
     |

  这里为了避免每次都输入varible所属对象的指针,使用了一个智能指针来提供一个左指针pLeft和一个右指针pRight语义,这个智能指针只需要实现取下标功能即可,因此可以简单实现如下(为了通用,必须为模板类):

template <typename TYPE>
class CSmartPointer{
protected:
    TYPE * m_pPointer;
public:
    CSmartPointer(TYPE * pPointer):m_pPointer(pPointer){};
    TYPE* operator->() {return m_pPointer;}
};

  这样,原来的代码就可以修改成这样:

CMyDoc * pDoc = GetDocument();
ASSERT_VALID(pDoc);

CParameterDlg dlg;
//设置对话框初值
DataExchange(pDoc,&dlg,FALSE);
//显示对话框
if(dlg.DoModal() == IDOK)
{
   //点击OK按钮后保存设置
    DataExchange(pDoc,&dlg,TRUE);
}  

  上述代码要求左右指针对应变量名必须相同,如果变量名不同,就不能这样使用,需要设计成这样的EXCHANGE2宏:

#define EXCHANGE2(leftvar,rightvar) \
if(flag)\
{\
    pLeft->leftvar,pRight->rightvar;\
}else{\
    pRight->rightvar = pLeft->leftvar;\
}

这样,对应的EXCHANGE子句需要修改成 EXCHANGE2(m_lValue1,m_dwValue2);

上述代码看起来是完美的,但是有一些特殊还是不正确,这些特殊情况就是=用于赋值不正确的情况。

有两种常见问题:
leftvar和rightvar分别是指针类型,但是其实想拷贝它们指向的缓冲区的内容(如字符串拷贝)。
为了控制显示精度,对话框控制变量是一个CString对象,它是文档对象中对应变量的格式化后的信息。最常见的是, leftvar是一个浮点数,需要以几个小数位格式输出,因此rightvar是一个CString对象。
为了实现上面的目的,就不能使用=来直接赋值,而应该用一个函数Assign(函数名当然可以任意取啦)来做这件事。为此,修改上述的EXCHANGE和EXCHANGE2宏如下:

#define EXCHANGE(var) \
    if(flag)\
    {\
        Assign(pLeft->var,pRight->var);\
    }else{\
        Assign(pRight->var,pLeft->var);\
    }
#define EXCHANGE2(leftvar,rightvar) \
   if(flag)\
    {\
        Assign(pLeft->leftvar,pRight->rightvar);\
    }else{\
        Assign(pRight->rightvar,pLeft->leftvar);\
    }      

  这样只要针对每个类型对实现一次Assign即可。由于C++允许重载,这显得很容易。需要实现的函数一般有:

函数功能
void Assign(CString & left,CString & right)直接赋值CString类型
void Assign(CString & left, float & fValue)格式化float数值到left
void Assign(float & fValue,CString & right)从字符串中读取出float
void Assign(CString & left, double& dValue)格式化double数值到left
void Assign(double& dValue,CString & right)从字符串中读取出double
void Assign(CString & left, int & iValue)格式化int数值到left
void Assign(int & iValue,CString & right)从字符串中读取出int
void Assign(CString & left, short& sValue)格式化short数值到left
void Assign(short & sValue,CString & right)从字符串中读取出short
void Assign(CString & left, long & lValue)格式化long数值到left
void Assign(long & lValue,CString & right)从字符串中读取出long
void Assign(CString & left, CTime & time)格式化CTime数值到left
void Assign(CTime & time,CString & right)从字符串中读取出CTime
  到底要实现哪些类型对,需要读者根据自己项目需要设计。
  • 3
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值