C++ 中常见预定义宏的使用

时间:2022-07-20 15:16:20

http://blog.csdn.net/hgl868/article/details/7058906

替代字符串:

#define DOWNLOAD_IMAGE_LOG /var/log/png.log

#define WGET_IMAGE_(x) "wget -b -c -P ./media/video -a " x " -i  mp4url.txt"

WGET_IMAGE_(DOWNLOAD_IMAGE_LOG)就是 wget -b -c -P ./media/video -a /var/log/png.log -i  mp4url.txt


C、C++宏体中出现的#,#@,##,


1,  #表字符串化(stringfication),如 

#define C(x) #x  

C(I am a string) ---> "I am a string"


2, ##表字符串连接(concatenation)

左边的代码可以通过宏字符串连接来简化:

如#define  cmd(x)  x ## _command

cmd(quit) 就是 quit_command

实用的例子如下:

struct command
{
char *name;
void (*function) (void);
}; struct command commands[] =
{
{ "quit", quit_command },
{ "help", help_command },
...
};
改成如下:

#define COMMAND(NAME)  { #NAME, NAME ## _command }

struct command commands[] =
{
COMMAND (quit),
COMMAND (help),
};

下面的代码利用宏字符串化来输出信息:

#define WARN_IF(expr)   do{ if(expr) {fprintf(stderr, "warning" #expr "\n");}}while(0)

使用do while的原因是为了让WARN_IF(expr)后面加分号使之看起来更像函数。


宏扩展
#include <stdio.h>
#define str(s) #s
#define xstr(s) str(s)
#define foo 4
int main(int argc, char *argv[])
{
printf ("%s\n",str(foo));
printf ("%s\n",xstr(foo));
return 0;
}
# 结果为foo 4

宏展开时先展开里层的宏,但包含#和##的宏不展开,由于str(foo)宏str包含#故foo不展开,而xstr宏中不包含#故foo展开为4,即str(4)再开展str得4


同理可知下面结果为: _G(A,B) AB

#define G(a,b) a##b
#define _G(a,b) G(a,b)
#define S(X) #X
#define _S(X) S(X)
#define A 1
#define B 2
#include<stdio.h>
int main()
{
printf("%s\n",S(_G(A,B)));
printf("%s\n",_S(G(A,B)));
return 0;
}

关于记号粘贴操作符(token paste operator): ##
1. 简单的说,“##”是一种分隔连接方式,它的作用是先分隔,然后进行强制连接。
   其中,分隔的作用类似于空格。我们知道在普通的宏定义中,预处理器一般把空格
   解释成分段标志,对于每一段和前面比较,相同的就被替换。但是这样做的结果是,
   被替换段之间存在一些空格。如果我们不希望出现这些空格,就可以通过添加一些
   ##来替代空格。
   另外一些分隔标志是,包括操作符,比如 +, -, *, /, [,], …,所以尽管下面的
   宏定义没有空格,但是依然表达有意义的定义: define add(a, b)  a+b
   而其强制连接的作用是,去掉和前面的字符串之间的空格,而把两者连接起来。
2. 举列 – 试比较下述几个宏定义的区别
   #define A1(name, type)  type name_##type##_type 
   #define A2(name, type)  type name##_##type##_type
   A1(a1, int);  /* 等价于: int name_int_type; */
   A2(a1, int);  /* 等价于: int a1_int_type;   */
   解释:
        1) 在第一个宏定义中,”name”和第一个”_”之间,以及第2个”_”和第二个
   ”type”之间没有被分隔,所以预处理器会把name_##type##_type解释成3段:
   “name_”、“type”、以及“_type”,这中间只有“type”是在宏前面出现过
    的,所以它可以被宏替换。
        2) 而在第二个宏定义中,“name”和第一个“_”之间也被分隔了,所以
   预处理器会把name##_##type##_type解释成4段:“name”、“_”、“type”
   以及“_type”,这其间,就有两个可以被宏替换了。
        3) A1和A2的定义也可以如下:
           #define A1(name, type)  type name_  ##type ##_type  
                                      <##前面随意加上一些空格>
           #define A2(name, type)  type name ##_ ##type ##_type
    结果是## 会把前面的空格去掉完成强连接,得到和上面结果相同的宏定义
3. 其他相关 – 单独的一个 #
   至于单独一个#,则表示 对这个变量替换后,再加双引号引起来。比如
      #define  __stringify_1(x)   #x
那么
      __stringify_1(linux)   <==>  ”linux”
所以,对于MODULE_DEVICE_TABLE
     1) #define MODULE_DEVICE_TABLE(type,name)   MODULE_GENERIC_TABLE(type##_device,name)
     2) #define MODULE_GENERIC_TABLE(gtype,name)      extern const struct gtype##_id __mod_##gtype##_table     
             __attribute__ ((unused, alias(__stringify(name))))
得到  
      MODULE_DEVICE_TABLE(usb, products)  
                             /*notes: struct usb_device_id products; */
 <==> MODULE_GENERIC_TABLE(usb_device,products)
 <==> extern const struct usb_device_id __mod_usb_device_table     
             __attribute__ ((unused, alias(”products”)))   
注意到alias attribute需要一个双引号,所以在这里使用了__stringify(name)来
给name加上双引号。另外,还注意到一个外部变量”__mod_usb_device_table”被alias
到了本驱动专用的由用户自定义的变量products<usb_device_id类型>。这个外部变量
是如何使用的,更多的信息请参看《probe()过程分析》。

在标准C以及各中编译器中定义了一些对象宏, 这些宏的名称以"__"开头和结尾, 并且都是大写字符. 这些预定义宏可以被#undef, 也可以被重定义。

在ANSI C标准中定义了__FILE__,__LINE__,__DATA__,__TIME__,__STDC__等标准的预定义宏。GCC对其进行扩展,也定义了多个预定义宏。

概括起来GCC中可使用的预定义宏涵盖了如下几方面的信息:
      1、宿主的信息:GNU的版本,编译器的版本,类型的相关信息,字节序信息等。
      2、编译动作的信息:编译的日期、时间;编译时是否进行了时间或空间上的优化;定义的inline是否被编译器执行等。
      3、文件的信息:文件名称、函数名称、行数信息、文件最后修改时间等等。
      4、计数信息:__COUNTER__,__INCLUDE_LEVEL__等。

下面是一些常见的预定义宏的使用方法。

1、__FILE__,__LINE__,FUNCTION__
      这是最常用到的预定义宏的组合,表示文件名、行数和函数名,用于程序运行期异常的跟踪。如:

//-------file main.c----------
#include <stdio.h>
#include "myassert.h"

int func(const char *filename);

int main(int argc,char **argv)
{
    MyAssert("two args are needed",argc==2);
    func(argv[1]);
    return 0;
}

//-------file func.c----------
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "myassert.h"

int func(const char *filename)
{
    int fd;
    MyAssert("filename can not be null",filename);
    MyAssert("file not exist",0==access(filename,F_OK));
    fd = open(filename,O_RDONLY);
    close(fd);
    return 0;
}

//-------file myassert.h----------
#ifndef __MY_ASSERT_H__
#define __MY_ASSERT_H__

#include <stdio.h>
#include <stdlib.h>

#define  MyAssert(message,assertion) do{/
    if(!(assertion)){/
        printf("line %d in %s(%s)", __LINE__, __FILE__,__FUNCTION__);/
        if(message){/
            printf(" : %s",message);/
        }/
        printf("/n");/
        abort();/
    }/
}while(0);
#endif

#Makefile
TARGET = test
CC = gcc
CCFLAGS = -Wall
OBJS = main.o func.o

$(TARGET) : $(OBJS)
    $(CC) -o $@ $(OBJS) $(CCFLAGS)

%.o : %.c
    $(CC) -o $@ -c $< $(CCFLAGS)

clean:
    rm -rf *.o 
    rm -rf $(TARGET)

可见通过使用__FILE__,__LINE__,FUNCTION__宏,可以帮助我们精确的定位出现异常的文件、函数和行数。

2、__BASE_FILE__

这个宏是和__FILE__相对应的,表示主输入文件的名字,对于源文件而言__FILE__和__BASE_FILE__是一样的;对于头文件二者才可能不同。比如在上个例子中,__LINE__这个宏是在myassert.h文件中定义的,被main.c和func.c包含之后__FILE__的值
分别变成了main.c和func.c。但是当我们希望知道MyAssert这个宏具体实在哪个文件(实际上是myassert.h)中定义的话,就需要用到__BASE_FILE__。
     下面的例子可以帮助加深理解:

//-------file main.c----------
#include <stdio.h>
#include "basefile.h"

int main(int argc, char *argv[])
{
    printf("%s/n",sfile);
    printf("%s/n",hfile);
    return 0;
}

//-------file basefile.h----------
const char sfile[]= __FILE__;
const char hfile[]= __BASE_FILE__;

gcc main.c &&./a.out 得到:

basefile.h
       main.c

 3、__DATE__,__TIME__
       用于得到最后一次编译的日期和时间(字符串形式):

#include <stdio.h>
//-------file main.c----------
int main()
{
    printf("DATE : %s/n",__DATE__);
    printf("TIME : %s/n",__TIME__);
}

gcc main.c &&./a.out 得到:
       DATE : Jan 27 2011
       TIME : 17:12:55

4、__TIMESTAMP__
       和__TIME__的格式相同。同于得到本文件最后一次被修改的时间。

5、__GNUC__、__GNUC_MINOR__、__GNUC_MINOR__、__GNUC_PATCHLEVEL__
       用于得到GNU版本:

#include <stdio.h>
int main()
{
    if( __GNUC__ > 4 || 
        (__GNUC__ == 4 && (__GNUC_MINOR__ > 2 ||  
            (__GNUC_MINOR__ == 2 && __GNUC_PATCHLEVEL__ > 0)))){
        printf("GNUC version is later than 3.3.2/n");
    }else{
        printf("GNUC version is older than 3.3.2/n");
    }

}

6、__VERSION__
     用于得到编译器的版本

//-------file main.c----------
#include <stdio.h>
int main()
{
    printf("Version : %s/n",__VERSION__);
    return 0;
}

gcc main.c && ./a.out得到:

Version : 4.1.2 (Gentoo 4.1.2 p1.0.2)
      可以和gcc -v相互验证

7、__COUNTER__
      自身计数器,用于记录以前编译过程中出现的__COUNTER__的次数,从0开始计数。常用于构造一系列的变量名称,函数名称等。如:
//-------file main.c----------
#include <stdio.h>

#define FUNC2(x,y) x##y
#define FUNC1(x,y) FUNC2(x,y)  
#define FUNC(x) FUNC1(x,__COUNTER__)

int FUNC(var);
int FUNC(var);

int main() {
    var0 = 0;
    var1 = 1;
    printf("%d/n",var0);
    printf("%d/n",var1);
    return 0;
}
      gcc main.c &&a.out得到结果:
      0
      1
      这里使用__COUNTER__构造了两个变量:var0,var1。

8、__INCLUDE_LEVEL__
      用于表示文件被包含的计数,从0开始递增,常作为递归包含的限制条件。如:

//-------file main.c----------
#include <stdio.h>
int main()
{
   #define REP_LIMIT 10
   #define REP(BLAH) printf("%d ", BLAH);
   #include "rep.h"
   printf("/n");
   return 0;
}

//--------file rep.h----------
#if __INCLUDE_LEVEL__ < REP_LIMIT
REP(__INCLUDE_LEVEL__)
#include "rep.h"
#endif

gcc main.c && ./a.out,得到结果:
      1 2 3 4 5 6 7 8 9
      在这个例子中文件rep.h自包含了9次,执行了9次REP(BLAH)。

实际上,__INCLUDE_LEVEL__最多的是和#include __FILE__组合使用,用于表示一个递归。如:

//-------file main.c----------
#ifndef AUTOINC
#define AUTOINC

#include <stdio.h>
#define MAX_LEVEL 10
int main()
{
    int i = 0;
    #include __FILE__
    printf("/n");
    return 0;
}
#undef AUTOINC
#endif

#ifdef AUTOINC
    #if __INCLUDE_LEVEL__ <= MAX_LEVEL
    printf("%d ",__INCLUDE_LEVEL__);
    #include __FILE__

#if __INCLUDE_LEVEL__ != MAX_LEVEL
    printf("%d ",__INCLUDE_LEVEL__);
    #endif

#endif
#endif
      gcc main.c && ./a.out得到结果:
      1 2 3 4 5 6 7 8 9 10 9 8 7 6 5 4 3 2 1

ANSI C标准中有几个标准预定义宏:

__FILE__     __DATE__   __TIME___    __LINE__   等

__LINE__:在源代码中插入当前源代码行号;

__FILE__:在源文件中插入当前源文件名;

__DATE__:在源文件中插入当前的编译日期

__TIME__:在源文件中插入当前编译时间;

__STDC__:当要求程序严格遵循ANSI C标准时该标识被赋值为1;

__cplusplus:当编写C++程序时该标识符被定义。

一、标准预定义宏
The standard predefined macros are specified by the relevant language standards, so they are available with all compilers that implement those standards. Older compilers may not provide all of them. Their names all start with double underscores.

__FILE__
This macro expands to the name of the current input file, in the form of a C string constant. This is the path by which the preprocessor opened the file, not the short name specified in #include or as the input file name argument. For example, "/usr/local/include/myheader.h" is a possible expansion of this macro.

__LINE__
This macro expands to the current input line number, in the form of a decimal integer constant. While we call it a predefined macro, it's a pretty strange macro, since its "definition" changes with each new line of source code.
__FILE__ and __LINE__ are useful in generating an error message to report an inconsistency detected by the program; the message can state the source line at which the inconsistency was detected. For example,

fprintf (stderr, "Internal error: "
                      "negative string length "
                      "%d at %s, line %d.",
              length, __FILE__, __LINE__);
    
An #include directive changes the expansions of __FILE__ and __LINE__ to correspond to the included file. At the end of that file, when processing resumes on the input file that contained the #include directive, the expansions of __FILE__ and __LINE__ revert to the values they had before the #include (but __LINE__ is then incremented by one as processing moves to the line after the #include).

A #line directive changes __LINE__, and may change __FILE__ as well. See Line Control.

C99 introduces __func__, and GCC has provided __FUNCTION__ for a long time. Both of these are strings containing the name of the current function (there are slight semantic differences; see the GCC manual). Neither of them is a macro; the preprocessor does not know the name of the current function. They tend to be useful in conjunction with __FILE__ and __LINE__, though.

__DATE__
This macro expands to a string constant that describes the date on which the preprocessor is being run. The string constant contains eleven characters and looks like "Feb 12 1996". If the day of the month is less than 10, it is padded with a space on the left.
If GCC cannot determine the current date, it will emit a warning message (once per compilation) and __DATE__ will expand to "??? ?? ????".

__TIME__
This macro expands to a string constant that describes the time at which the preprocessor is being run. The string constant contains eight characters and looks like "23:59:01".
If GCC cannot determine the current time, it will emit a warning message (once per compilation) and __TIME__ will expand to "??:??:??".

__STDC__
In normal operation, this macro expands to the constant 1, to signify that this compiler conforms to ISO Standard C. If GNU CPP is used with a compiler other than GCC, this is not necessarily true; however, the preprocessor always conforms to the standard unless the -traditional-cpp option is used.
This macro is not defined if the -traditional-cpp option is used.

On some hosts, the system compiler uses a different convention, where __STDC__ is normally 0, but is 1 if the user specifies strict conformance to the C Standard. CPP follows the host convention when processing system header files, but when processing user files __STDC__ is always 1. This has been reported to cause problems; for instance, some versions of Solaris provide X Windows headers that expect __STDC__ to be either undefined or 1. See Invocation.

__STDC_VERSION__
This macro expands to the C Standard's version number, a long integer constant of the form yyyymmL where yyyy and mm are the year and month of the Standard version. This signifies which version of the C Standard the compiler conforms to. Like __STDC__, this is not necessarily accurate for the entire implementation, unless GNU CPP is being used with GCC.
The value 199409L signifies the 1989 C standard as amended in 1994, which is the current default; the value 199901L signifies the 1999 revision of the C standard. Support for the 1999 revision is not yet complete.

This macro is not defined if the -traditional-cpp option is used, nor when compiling C++ or Objective-C.

__STDC_HOSTED__
This macro is defined, with value 1, if the compiler's target is a hosted environment. A hosted environment has the complete facilities of the standard C library available.

__cplusplus
This macro is defined when the C++ compiler is in use. You can use __cplusplus to test whether a header is compiled by a C compiler or a C++ compiler. This macro is similar to __STDC_VERSION__, in that it expands to a version number. A fully conforming implementation of the 1998 C++ standard will define this macro to 199711L. The GNU C++ compiler is not yet fully conforming, so it uses 1 instead. We hope to complete our implementation in the near future.

__OBJC__
This macro is defined, with value 1, when the Objective-C compiler is in use. You can use __OBJC__ to test whether a header is compiled by a C compiler or a Objective-C compiler.

__ASSEMBLER__
This macro is defined with value 1 when preprocessing assembly language.

__DATE__ 进行预处理的日期(“Mmm dd yyyy”形式的字符串文字,如May 27 2006)
__FILE__ 代表当前源代码文件名的字符串文字 ,包含了详细路径,如G:/program/study/c+/test1.c
__LINE__ 代表当前源代码中的行号的整数常量
__TIME__ 源文件编译时间,格式微“hh:mm:ss”,如:09:11:10;
__func__ 当前所在函数名,在编译器的较高版本中支持
__FUNCTION__ 当前所在函数名

  对于__FILE__,__LINE__,__func__,__FUNCTION__ 这样的宏,在调试程序时是很有用的,因为你可以很容易的知道程序运行到了哪个文件的那一行,是哪个函数。
而对于__DATE__,__TIME__则可以获取编译时间,如如下代码通过宏获取编译时间,并通过sscanf()从中获取具体的年月日时分秒数据,可在代码中做相应使用。我的代码中是根据此数据作为版本标识,并依此判断哪个版本新些及是否需要升级。
char * creationDate   = __DATE__ ", " __TIME__;
sscanf(creationDate, "%s %d %d, %d:%d:%d", month, &day, &year, &hour, &min, &sec);

预处理命令#pragma和预定义宏--转载

一、C预定义宏
C标准指定了一些预定义宏,编程中常常用到。
__DATE__     进行预处理的日期
__FILE__     代表当前源代码文件名的字符串
__LINE__     代表当前源代码文件中行号的整数常量
__STDC__     设置为1时,表示该实现遵循C标准
__STDC_HOSTED__  为本机环境设置为,否则设为0
__STDC_VERSION__ 为C99时设置为199901L
__TIME__     源文件的编译时间
__func__     C99提供的,为所在函数名的字符串
对于__FILE__,__LINE__,__func__这样的宏,在调试程序时是很有用的,因为你可以很容易的知道程序运行到了哪个文件的那一行,是哪个函数.

例如:
#include
#include
void why_me();
int main()
{
    printf( "The file is %s/n", __FILE__ );
    printf( "The date is %s/n", __DATE__ );
    printf( "The time is %s/n", __TIME__ );
    printf("The version is %s/n",__STDC__VERSION__);
    printf( "This is line %d/n", __LINE__ );
    printf( "This function is %s/n ", __func__ );
   why_me();
   return 0;
}
void why_me()
{
    printf( "This function is %s/n", __func__ );
    printf( "This is line %d/n", __LINE__ );
}

二、#line和#error
#line用于重置由__LINE__和__FILE__宏指定的行号和文件名。
用法如下:#line number filename
例如:#line 1000 //将当前行号设置为1000
     #line 1000 "lukas.c"   //行号设置为1000,文件名设置为lukas.c

#error指令使预处理器发出一条错误消息,该消息包含指令中的文本.这条指令的目的就是在程序崩溃之前能够给出一定的信息。

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

(1)message 参数。 Message 参数是我最喜欢的一个参数,它能够在编译信息输出窗口中输出相应的信息,这对于源代码信息的控制是非常重要的。其使用方法为:
#Pragma message(“消息文本”)
当编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来。
当我们在程序中定义了许多宏来控制源代码版本的时候,我们自己有可能都会忘记有没有正确的设置这些宏,此时我们可以用这条指令在编译的时候就进行检查。假设我们希望判断自己有没有在源代码的什么地方定义了_X86这个宏可以用下面的方法
#ifdef _X86
#Pragma message(“_X86 macro activated!”)
#endif
当我们定义了_X86这个宏以后,应用程序在编译时就会在编译输出窗口里显示“_
X86 macro activated!”。我们就不会因为不记得自己定义的一些特定的宏而抓耳挠腮了。

(2)另一个使用得比较多的pragma参数是code_seg。格式如:
#pragma code_seg( ["section-name"[,"section-class"] ] )
它能够设置程序中函数代码存放的代码段,当我们开发驱动程序的时候就会使用到它。

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

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

(5)#pragma resource "*.dfm"表示把*.dfm文件中的资源加入工程。*.dfm中包括窗体外观的定义。

(6)#pragma warning( disable : 4507 34; once : 4385; error : 164 )等价于:
#pragma warning(disable:4507 34) /* 不显示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)。

(7)pragma comment(...)
该指令将一个注释记录放入一个对象文件或可执行文件中。
常用的lib关键字,可以帮我们连入一个库文件。 

(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()测试结构体的长度!

在你写dll的时候,因为对于C和C++,编译器会有不同的名字解析规则,所以可以这样用

#ifndef   __STDC__

extern "C "   void   function();

#else

void   function();

#endif

__LINE__           在源代码中插入当前源代码行号
  __FILE__           在源代码中插入当前源代码文件名
  __DATE__           在源代码中插入当前编译日期〔注意和当前系统日期区别开来〕
  __TIME__           在源代码中插入当前编译时间〔注意和当前系统时间区别开来〕  
  __STDC__           当要求程序严格遵循ANSIC标准时该标识符被赋值为1。
----------------------------------------------------------------------------

标识符__LINE__和__FILE__通常用来调试程序;标识符__DATE__和__TIME__通常用来在编译后的程序中加入一个时间标志,以区分程序的不同版本;当要求程序严格遵循ANSIC标准时,标识符__STDC__就会被赋值为1;当用C++编译程序编译时,标识符__cplusplus就会被定义。

#include

int main ()

{

printf("该输出行在源程序中的位置:%d/n", __LINE__ );

printf("该程序的文件名为:%s/n", __FILE__ );

printf("当前日期为:%s/n", __DATE__ );

printf("当前时间为:%s/n", __TIME__ );

return 0;

}

#include

void main(void)

{

printf("%d",__LINE__); // Line 5

}

结果为:5

// 标准预定义宏宏.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#include

void main(void)

{
 
    printf("%d",__LINE__); // Line 5

}

编译器宏使用总结
 
 
 

C/C++中宏总结C程序的源代码中可包括各种编译指令,这些指令称为预处理命令。虽然它们实际上不是C语言的一部分,但却扩展了C程序设计的环境。本节将介绍如何应用预处理程序和注释简化程序开发过程,并提高程序的可读性。ANSI标准定义的C语言预处理程序包括下列命令:

#define,#error,#include,#if,#else,#elif,#endif,#ifdef,#ifndef,#undef,#line,#pragma等。非常明显,所有预处理命令均以符号#开头,下面分别加以介绍。

命令#define定义了一个标识符及一个串。在源程序中每次遇到该标识符时,均以定义的串代换它。ANSI标准将标识符定义为宏名,将替换过程称为宏替换。命令的一般形式为:

#define identifier string

注意:

? 该语句没有分号。在标识符和串之间可以有任意个空格,串一旦开始,仅由一新行结束。

? 宏名定义后,即可成为其它宏名定义中的一部分。

? 宏替换仅仅是以文本串代替宏标识符,前提是宏标识符必须独立的识别出来,否则不进行替换。例如:

#define XYZ this is a tes

使用宏printf("XYZ");//该段不打印"this is a test"而打印"XYZ"。因为预编译器识别出的是"XYZ"

? 如果串长于一行,可以在该行末尾用一反斜杠' /'续行。

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

#include 命令#i nclude使编译程序将另一源文件嵌入带有#i nclude的源文件,被读入的源文件必须用双引号或尖括号括起来。例如:

#i nclude"stdio.h"或者#i nclude

这两行代码均使用C编译程序读入并编译用于处理磁盘文件库的子程序。

将文件嵌入#i nclude命令中的文件内是可行的,这种方式称为嵌套的嵌入文件,嵌套层次依赖于具体实现。

如果显式路径名为文件标识符的一部分,则仅在哪些子目录中搜索被嵌入文件。否则,如果文件名用双引号括起来,则首先检索当前工作目录。如果未发现文件,则在命令行中说明的所有目录中搜索。如果仍未发现文件,则搜索实现时定义的标准目录。

如果没有显式路径名且文件名被尖括号括起来,则首先在编译命令行中的目录内检索

如果文件没找到,则检索标准目录,不检索当前工作目录。

条件编译命令

有几个命令可对程序源代码的各部分有选择地进行编译,该过程称为条件编译。商业软件公司广泛应用条件编译来提供和维护某一程序的许多顾客版本。

#if、#else,#elif及#endif

#if的一般含义是如果#if后面的常量表达式为true,则编译它与#endif之间的代码,否则跳过这些代码。命令#endif标识一个#if块的结束。

#if constant-expression

statement sequence

#endif

跟在#if后面的表达式在编译时求值,因此它必须仅含常量及已定义过的标识符,不可使用变量。表达式不许含有操作符sizeof(sizeof也是编译时求值)。

#else命令的功能有点象C语言中的else;#else建立另一选择(在#if失败的情况下)。注意,# else属于# if块。

#elif命令意义与ELSE IF 相同,它形成一个if else-if阶梯状语句,可进行多种编译选择。#elif 后跟一个常量表达式。如果表达式为true,则编译其后的代码块,不对其它#elif表达式进行测试。否则,顺序测试下一块。

#if expression

statement sequence

#elif expression1

statement sequence

#endif

在嵌套的条件编译中#endif、#else或#elif与最近#if或#elif匹配。

# ifdef 和# ifndef

条件编译的另一种方法是用#ifdef与#ifndef命令,它们分别表示"如果有定义"及"如果无定义"。# ifdef的一般形式是:

# ifdef macroname

statement sequence

#endif

#ifdef与#ifndef可以用于#if、#else,#elif语句中,但必须与一个#endif。

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

#undef macroname

命令# line改变__LINE__与__FILE__的内容,它们是在编译程序中预先定义的标识符。命令的基本形式如下:

# line number["filename"]

其中的数字为任何正整数,可选的文件名为任意有效文件标识符。行号为源程序中当前行号,文件名为源文件的名字。命令# line主要用于调试及其它特殊应用。注意:在#line后面的数字标识从下一行开始的数字标识。

预定义的宏名

 

ANSI标准说明了C中的五个预定义的宏名。它们是:

 

__LINE__

__FILE__

__DATE__

__TIME__

__STDC__

如果编译不是标准的,则可能仅支持以上宏名中的几个,或根本不支持。记住编译程序也许还提供其它预定义的宏名。

__LINE__及__FILE__宏指令在有关# line的部分中已讨论,这里讨论其余的宏名。

__DATE__宏指令含有形式为月/日/年的串,表示源文件被翻译到代码时的日期。

源代码翻译到目标代码的时间作为串包含在__TIME__中。串形式为时:分:秒。

如果实现是标准的,则宏__STDC__含有十进制常量1。如果它含有任何其它数,则实现是非标准的。编译C++程序时,编译器自动定义了一个预处理名字__cplusplus,而编译标准C时,自动定义名字__STDC__。

注意:宏名的书写由标识符与两边各二条下划线构成。