⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 28.txt

📁 一個計算機系教授的上課講義 主要是教導學生使用C語言編寫程序
💻 TXT
字号:
CS 1355
Introduction to Programming in C
Monday 2006.12.25
Lecture notes (at http://r638-2.cs.nthu.edu.tw/C/notes/28.txt)
Today: Chapter 13: C Preprocessor

Pre-processing: rewrites the program source code
- read a C source file (with macros), 
  remove comments,
  perform "macro expansion",
  output another C source file (without macros)
- then, a C compiler compiles the preprocessed source file.

Why?
- symbolic constants (#define )
- reduces complexity of the C compiler (#define )
 => compiler doesn't have to handle so many features itself!
- maintaining alternative versions of program in one file
  (using conditional compilation with #if)
- share source code (with #include) for separately compiled files

All preprocessor directives start with #

Symbolic constants
#define PI  3.14159
#define NIL ((Tree)-1)
#define ARRAYSIZE  20

What the preprocessor does: text substitution
=> everywhere in the source code except string literals

e.g., 
	int data[ARRAYSIZE];
after macro expansion, looks like
	int data[20];

You can define any text up to the end of a line, except quote marks
(" or ')
Even ( and ) can be part of the macro!

Example:
#include <stdio.h>
#define LeftParen  (
#define RightParen )

int main LeftParen RightParen {
        printf LeftParen"hello world\n"RightParen;
}

This works! LeftParen gets expanded into (
and RightParen gets expanded into )
Similarly, braces, brackets also work.

This is not necessarily easier to read, but could be useful.

You can't do
#define DoubleQuote "
#define SingleQuote '
=> C preprocessor will complain about both being unterminated

Q: What if you want to make a substitution inside a string literal,
  similar to sprintf(buf, "hello %s world", "zzz")?
A: just put multiple string literals together!

#define Hello "hello"
#define World "world"
int main() {
	printf(Hello " " World "\n");
}
=> it will join the string literals together into one string
   "hello world\n"

Macros can be parameterized!

#define CIRCLE_AREA(r) PI*(r)*(r)

This way, you can say
	float a = CIRCLE_AREA(20.0);

It looks like a function call, but it is not!
It is "macro expansion", expanded into
	float a = 3.14159 * (20.0) * (20.0);

This means it does text substitution, without evaluation.
Having the ( ) is important, because otherwise you might get
unexpected results.
For example,
	float x = 20.0;
	float a = CIRCLE_AREA(x + 3);
This gets expanded into
	float a = 3.14159 * (x + 3) * (x + 3);

If you did not have ( ) around r in the macro definition, 
that is,
#define CIRCLE_AREA(r)  PI * r * r

Then, it gets expanded into
	float a = 3.14159 * x + 3 * x + 3;

which is probably not what you want! because * has higher precedence than +

Why macro this way?
- this is called "inlining":
  it means instead of a function call, 
  just "inline" (substitute in) the function call with the expression 
  that computes the function.
- this can be more efficient, because function call can be expensive
  (need to pass parameters, push stack, save registers, restore registers,
   pop stack, retrieve return value from stack...)
  whereas inlining eliminates runtime overhead
  - however, the program size can get bigger, because
    each function call is substituted by a whole expression

Other example use: in standard I/O
#define getchar() getc(stdin)
#define putchar() putc(stdout)

Many macros for 
ctype.h
	e.g., 
	#define isalnum(c) __istype((c), (_CTYPE_A|_CTYPE_D))


Another difference: macro parameters are typeless.
- for functions, you have to define the types of the parameters and
  of the return value.
- for macros, the same parameter can be of different types!
  - this is called "overloading".

Yet another difference: you can "undefine" a parameter!
#undef NULL

Conditional compilation
- You can test whether a symbol has been defined
- similar to if/then/else statement, except it's a compile-time decision
Two versions
	#ifdef   /* if defined */
	#ifndef  /* if not defined */

Example use:
#ifdef getchar
#undef getchar
#endif
#define getchar(stdin)

Common usage: #include of header files
C convention: define a name that corresponds to a header file,
=> so you can test whether a header file has been included
=> avoid repeated inclusion of a file

For example, if you have a header file "morse.h" for morse code:

#ifndef __MORSE_H__
#define __MORSE_H__
void encode(void);
void decode(void);
#endif __MORSE_H__

The convention is to use __ before and after the tag,
and change morse.h into all upper case, and change . into _

For the first time, __MORSE_H__ is not defined, so the #ifndef part
evaluates to true and includes everything up to #endif.

If the user includes morse.h again, then it will skip all this part.

Actually, you can also say

#if defined(__MORSE__)		instead of #ifdef __MORSE__
#if !defined(__MORSE__)		instead of #ifndef __MORSE__

both are preprocessor directives.

Very important:
- #ifdef etc check if the symbol is defined by another #define directive.
- If you declare the name as a variable in C, the preprocessor does not see it!!

#include <stdio.h>
int main() {
	int x = 30;
#ifdef x
	printf("x is defined\n");
#else
	printf("x is not defined\n");
#endif
}
% ./a.out
x is not defined

For general #if, you can actually do comparisons

#define MAX 20
#if MAX > 20
#define MIN 20
#else
#define MIN 0
printf("MAX=%d, MIN=%d\n", MAX, MIN);
This works for number constants.
In fact, you can also have arithmetic expressions.
They get "folded" (evaluated) to constants at preprocessing time
as long as all arguments are constants.

Conditional macros are very useful for debugging.

#define DEBUG
#ifdef DEBUG
	printf("debug: x = %d\n", x);
#endif

Then, you can just define or undefine DEBUG at the top.
Another advanced use is to define debugging level

#define DEBUGLEVEL 0	/* could be 0, 1, 2, ... whatever you define */
#if DEBUGLEVEL > 3
	printf("debug level %d: x = %d\n", DEBUGLEVEL, x);
#endif DEBUGLEVEL	/* you can add this to enhance readability */

These #ifdef macros can also be used similar to comments.
#if 0	/* this is like a comment up to #endif */
	your comment here
#endif 0

What if you want to report an error?
#ifndef MAX
#error MAX is not defined
#endif

The error message gets printed.

Compiler-specific directives
#pragma Compiler-specific commands
=> tells  the specific compiler additional things for compilation.

example
#pragma GCC dependency "/usr/include/time.h" rerun fixincludes

something like that. Other compilers would ignore it.

Joining strings: Two ways:
- first, just use string literals
#define Hello1(x) "Hello " x
and pass the argument
	Hello1("John")		/* with quotes */

- second way: use # operator
#define Hello2(x) "Hello " #x
and pass the argument
	Hello2(John)		/* without quotes */

in both cases, it expands into string
	"Hello " "John"
which then is joined by the preprocessor into one string
	"Hello John"

To concatenaate name, use ##

#define JOIN(prefix, suffix)	prefix ## suffix

so, if you say
	JOIN(abc, xyz)

it expands into the identifier    abcxyz

To concatenate, some use /**/ as the zero-space separator
#define JOIN(prefix, suffix)   prefix/**/suffix

Line numbers, file names:
__LINE__
__FILE__
__DATE__
__TIME__

- you can force line numbers in file:
#line 100
	/* this tells the compiler that it corresponds to source line 100 */
Very useful when you pre-process a source file and output another source file.
=> it tells you which line it is in the original source file.

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -