Preprocessor directives
Preprocessor directives เป็นบรรทัดคำสั่งในโปรแกรมที่จะถูกประมวลผลก่อนที่คอมไพล์เลอร์จะทำงาน มันทำงานโดย Preprocessor และคำสั่งเหล่านี้จะต้องใส่เครื่องหมาย (#) นำหน้า ข้อแตกต่างจากคำสั่งปกติคือมันจะไม่มีเซมิโคลอน (;) หลังจากคำสั่ง เพราะว่ามันใช้การขึ้นบรรทัดใหม่เป็นตัวบ่งบอก แต่ถ้าเราต้องการให้มันอยู่ในบรรทัดเดียวกัน เราสามารถใช้เครื่องหมายขึ้นบรรทัดใหม่ได้ (/).
macro definitions (#define, #undef)
Marco definitions เป็นชุดของคำสั่งที่ใช้คำสั่ง
define
และ undef
มีรูปแบบคือ:#define identifier replacement
คำสั่งนี้ในแทนที่โค้ดของโปรแกรมที่ปรากฏเหมือน
identifier
และถ้าพบมันจะแทนที่โค้ดนั้นด้วย replacement
ตัวอย่างเช่น:#define SIZE 10
int number[SIZE];
int x = SIZE;
สังเกตุว่าคำสั่งนี้ไม่มีเซมิโคลอน และเราเห็นว่า
SIZE
นั้นเป็น identifier
ของคำสั่งนี้ และ 10
เป็น replacement
โดยมันจะค้นหาทุกโค้ดในโปรแกรมสำหรับ "SIZE"
และแทนที่ด้วย "10"
ดังนั้นโค้ดข้างบนจึงมีค่าเหมือนกันกับโค้ดต่อไปนี้int number[10];
int x = 10;
เราสามารถยกเลิก (undefine) directives ใดๆ ที่เราสร้างไปโดยการใช้คำสั่ง
undef
ของ macro ตัวอย่างเช่น#undef SIZE
Conditional inclusions (#ifdef, #ifndef, #if, #endif, #else and #elif)
Conditional inclusions เป็นคำสั่งที่ถูกใช้เพื่อตรวจสอบว่า Macro directives ได้ถูกสร้างก่อนหน้านี้แล้วหรือไม่ และไม่สำคัญไม่ว่าค่ามันจะเป็นอะไร
#ifdef
ใช้เพื่อตรวจสอบ Marco identifier ที่ระบุ ถ้ามันถูกสร้างไว้ก่อนหน้า ถ้าเป็นจริงมันจะอนุญาติให้ส่วนของโค้ดคอมไพล์ได้ ถ้าไม่ใช่มันจะข้ามการคอมไพล์โค้ดส่วนนั้นไป ส่วนของโค้ดของคำสั่งหรือบล็อคคำสั่งของ directive จะจบด้วย #endif
#define SIZE 100
#ifdef SIZE
int number[SIZE];
#endif;
และคำสั่งที่ทำงานตรงกันข้ามกับ
#ifdef
คือ #ifndef
คำสั่งนี้จะตรวจสอบถ้า Marco ไม่ได้ถูกสร้างก่อนหน้า ถ้าเป็นจริงมันจะอนุญาติให้ส่วนของโค้ดคอมไพล์ ถ้าไม่มันจะข้ามไป#ifndef SIZE
#define SIZE 100
#endif;
คำสั่ง
#if
, #endif
, #else
และ #elif
เพื่อที่จะตรวจสอบเงื่อนไขบางอย่างกับ Marco โดยที่ Expression สามารถที่จะเปรียบเทียบกับค่าคงที่ได้ มันทำงานเหมือนคำสั่ง if, else และ else ที่เราได้เรียนไปก่อนหน้านี้แล้ว#if SIZE>100
#undef SIZE
#define SIZE 200
#elif SIZE<50
#undef SIZE
#define SIZE 50
#else
#undef SIZE
#define SIZE 100
#endif
int myarray[SIZE];
Line control (#line)
#line directive
เป็นคำสั่งที่อนุญาติให้เราสามารถควบคุมว่าโปรแกรมจะแสดงข้อผิดพลาดอย่างไร โดยปกติ เมื่อมีข้อผิดพลาดเกิดขึ้นขณะการคอมไพล์ของโปรแกรม มันจะแสดงชื่อของไฟล์และบรรทัดที่ทำให้เกิดข้อผิดพลาดนั้น ดังนั้นเราสามารถเปลี่ยนชื่อไฟล์และบรรทัดที่เราต้องการจะให้มันแสดง รูปแบบของมันคือ: #line number "filename"
โดย
number
เป็นหมายเลขของบรรทัดที่อยู่ใต้คำสั่งนี้ และมันจะนับต่อจากบรรทัดนี้เพื่อให้สิ้นสุดไฟล์ ตัวอย่างเช่น:#line 100 "my file name"
int n=;
Error directive (#error)
Error directive ถูกใช้เพื่อหยุดการคอมไพล์ในบางโอกาส มันมักจะถูกใช้กับ Conditional directives ถ้ามีเงื่อนไขที่ตรง
#ifndef SIZE
#error A macro SIZE must define to run the program!
#endif
Source file inclusion (#include)
Source file inclusion directive ถูกใช้เพื่อนำเข้า Header หรือไฟล์มายังไฟล์โปรแกรมปัจจุบัน คุณได้เห็นไปแล้วในบทก่อนหน้าของบทเรียนนี้ รูปแบบของมันคือ:
#include <header>
#include "filename"
#include
directive จะแทนที่บรรทัดนั้นกับ Header หรือไฟล์ที่เรานำเข้ามา โดยไฟล์ Header จะต้องอยู่ในโฟล์เดอร์ของ Header ไฟล์#include <iostream>
#include "myheader.h"
ในตัวอย่างนี้ คุณได้เห็นแล้วหลายครั้ง โดยอันแรกจะปิดด้วย
<>
นั่นหมายถึงไลบรารี่มาตรฐานของ iostream
และอีกอันหนึ่งจะล้อมรอบด้วยเครื่องหมาย ""
(Double quote) ซึ่งเป็นไฟล์ Header ที่เราสามารถสร้างขึ้นมาเอง เพื่อให้ในสถานการณ์ที่เหมาะสมได้
****************************************************************************************************************************************
C++ Preprocessors
As the name suggests Preprocessors are programs that process our source code before compilation. There are a number of steps involved between writing a program and executing a program in C / C++. Let us have a look at these steps before we actually start learning about Preprocessors.
You can see the intermediate steps in the above diagram. The source code written by programmers is stored in the file program.c. This file is then processed by preprocessors and an expanded source code file is generated named program. This expanded file is compiled by the compiler and an object code file is generated named program .obj. Finally, the linker links this object code file to the object code of the library functions to generate the executable file program.exe.
Preprocessor programs provide preprocessors directives which tell the compiler to preprocess the source code before compiling. All of these preprocessor directives begin with a ‘#’ (hash) symbol. The ‘#’ symbol indicates that, whatever statement start with #, is going to preprocessor program, and preprocessor program will execute this statement. Examples of some preprocessor directives are: #include, #define, #ifndef etc. Remember that # symbol only provides a path that it will go to preprocessor, and command such as include is processed by preprocessor program. For example include will include extra code to your program. We can place these preprocessor directives anywhere in our program.
There are 4 main types of preprocessor directives:
- Macros
- File Inclusion
- Conditional Compilation
- Other directives
Let us now learn about each of these directives in details.
- Macros: Macros are a piece of code in a program which is given some name. Whenever this name is encountered by the compiler the compiler replaces the name with the actual piece of code. The ‘#define’ directive is used to define a macro. Let us now understand the macro definition with the help of a program:
- C
- C++
Output:0 1 2 3 4
In the above program, when the compiler executes the word LIMIT it replaces it with 5. The word ‘LIMIT’ in the macro definition is called a macro template and ‘5’ is macro expansion.
Note: There is no semi-colon(‘;’) at the end of macro definition. Macro definitions do not need a semi-colon to end.Macros with arguments: We can also pass arguments to macros. Macros defined with arguments works similarly as functions. Let us understand this with a program:- C
- C++
Output:Area of rectangle is: 50
We can see from the above program that whenever the compiler finds AREA(l, b) in the program it replaces it with the statement (l*b) . Not only this, the values passed to the macro template AREA(l, b) will also be replaced in the statement (l*b). Therefore AREA(10, 5) will be equal to 10*5. - File Inclusion: This type of preprocessor directive tells the compiler to include a file in the source code program. There are two types of files which can be included by the user in the program:
- Header File or Standard files: These files contains definition of pre-defined functions like printf(), scanf() etc. These files must be included for working with these functions. Different function are declared in different header files. For example standard I/O funuctions are in ‘iostream’ file whereas functions which perform string operations are in ‘string’ file.
Preprocessor directives
#
). These lines are not program statements but directives for the preprocessor. The preprocessor examines the code before actual compilation of code begins and resolves all these directives before any code is actually generated by regular statements.These preprocessor directives extend only across a single line of code. As soon as a newline character is found, the preprocessor directive is ends. No semicolon (
;
) is expected at the end of a preprocessor directive. The only way a preprocessor directive can extend through more than one line is by preceding the newline character at the end of the line by a backslash (\
).macro definitions (#define, #undef)
To define preprocessor macros we can use#define
. Its syntax is:#define identifier replacement
When the preprocessor encounters this directive, it replaces any occurrence of
identifier
in the rest of the code by replacement
. This replacement
can be an expression, a statement, a block or simply anything. The preprocessor does not understand C++ proper, it simply replaces any occurrence of identifier
by replacement
.
|
|
After the preprocessor has replaced
TABLE_SIZE
, the code becomes equivalent to:
|
|
#define
can work also with parameters to define function macros:
|
|
This would replace any occurrence of
getmax
followed by two arguments by the replacement expression, but also replacing each argument by its identifier, exactly as you would expect if it was a function:
|
| 5 7 |
Defined macros are not affected by block structure. A macro lasts until it is undefined with the
#undef
preprocessor directive:
|
|
This would generate the same code as:
|
|
Function macro definitions accept two special operators (
#
and ##
) in the replacement sequence:The operator
#
, followed by a parameter name, is replaced by a string literal that contains the argument passed (as if enclosed between double quotes):
|
|
This would be translated into:
|
|
The operator
##
concatenates two arguments leaving no blank spaces between them:
|
|
This would also be translated into:
|
|
Because preprocessor replacements happen before any C++ syntax check, macro definitions can be a tricky feature. But, be careful: code that relies heavily on complicated macros become less readable, since the syntax expected is on many occasions different from the normal expressions programmers expect in C++.
Conditional inclusions (#ifdef, #ifndef, #if, #endif, #else and #elif)
These directives allow to include or discard part of the code of a program if a certain condition is met.
#ifdef
allows a section of a program to be compiled only if the macro that is specified as the parameter has been defined, no matter which its value is. For example:
|
|
In this case, the line of code
int table[TABLE_SIZE];
is only compiled if TABLE_SIZE
was previously defined with #define
, independently of its value. If it was not defined, that line will not be included in the program compilation.#ifndef
serves for the exact opposite: the code between #ifndef
and #endif
directives is only compiled if the specified identifier has not been previously defined. For example:
|
|
In this case, if when arriving at this piece of code, the
TABLE_SIZE
macro has not been defined yet, it would be defined to a value of 100. If it already existed it would keep its previous value since the #define
directive would not be executed.The
#if
, #else
and #elif
(i.e., "else if") directives serve to specify some condition to be met in order for the portion of code they surround to be compiled. The condition that follows #if
or #elif
can only evaluate constant expressions, including macro expressions. For example:
|
|
Notice how the entire structure of
#if
, #elif
and #else
chained directives ends with #endif
.The behavior of
#ifdef
and #ifndef
can also be achieved by using the special operators defined
and !defined
respectively in any #if
or #elif
directive:
|
|
Line control (#line)
When we compile a program and some error happens during the compiling process, the compiler shows an error message with references to the name of the file where the error happened and a line number, so it is easier to find the code generating the error.The
#line
directive allows us to control both things, the line numbers within the code files as well as the file name that we want that appears when an error takes place. Its format is:#line number "filename"
Where
number
is the new line number that will be assigned to the next code line. The line numbers of successive lines will be increased one by one from this point on."filename"
is an optional parameter that allows to redefine the file name that will be shown. For example:
|
|
This code will generate an error that will be shown as error in file
"assigning variable"
, line 20.Error directive (#error)
This directive aborts the compilation process when it is found, generating a compilation error that can be specified as its parameter:
|
|
This example aborts the compilation process if the macro name
__cplusplus
is not defined (this macro name is defined by default in all C++ compilers).Source file inclusion (#include)
This directive has been used assiduously in other sections of this tutorial. When the preprocessor finds an#include
directive it replaces it by the entire content of the specified header or file. There are two ways to use #include
:
|
|
In the first case, a header is specified between angle-brackets
<>
. This is used to include headers provided by the implementation, such as the headers that compose the standard library (iostream
, string
,...). Whether the headers are actually files or exist in some other form is implementation-defined, but in any case they shall be properly included with this directive.The syntax used in the second
#include
uses quotes, and includes a file. The file is searched for in an implementation-defined manner, which generally includes the current path. In the case that the file is not found, the compiler interprets the directive as a header inclusion, just as if the quotes (""
) were replaced by angle-brackets (<>
).Pragma directive (#pragma)
This directive is used to specify diverse options to the compiler. These options are specific for the platform and the compiler you use. Consult the manual or the reference of your compiler for more information on the possible parameters that you can define with#pragma
.If the compiler does not support a specific argument for
#pragma
, it is ignored - no syntax error is generated.Predefined macro names
The following macro names are always defined (they all begin and end with two underscore characters,_
):macro | value |
---|---|
__LINE__ | Integer value representing the current line in the source code file being compiled. |
__FILE__ | A string literal containing the presumed name of the source file being compiled. |
__DATE__ | A string literal in the form "Mmm dd yyyy" containing the date in which the compilation process began. |
__TIME__ | A string literal in the form "hh:mm:ss" containing the time at which the compilation process began. |
__cplusplus | An integer value. All C++ compilers have this constant defined to some value. Its value depends on the version of the standard supported by the compiler:
|
__STDC_HOSTED__ | 1 if the implementation is a hosted implementation (with all standard headers available)0 otherwise. |
The following macros are optionally defined, generally depending on whether a feature is available:
macro | value |
---|---|
__STDC__ | In C: if defined to 1 , the implementation conforms to the C standard.In C++: Implementation defined. |
__STDC_VERSION__ | In C:
|
__STDC_MB_MIGHT_NEQ_WC__ | 1 if multibyte encoding might give a character a different value in character literals |
__STDC_ISO_10646__ | A value in the form yyyymmL , specifying the date of the Unicode standard followed by the encoding of wchar_t characters |
__STDCPP_STRICT_POINTER_SAFETY__ | 1 if the implementation has strict pointer safety (see get_pointer_safety ) |
__STDCPP_THREADS__ | 1 if the program can have more than one thread |
Particular implementations may define additional constants.
For example:
|
| This is the line number 7 of file /home/jay/stdmacronames.cpp. Its compilation began Nov 1 2005 at 10:12:29. The compiler gives a __cplusplus value of 1 |
************************************************************************************************************************