16 C预处理器和C库

2 明示常量:#define

#define预处理器指令和其他预处理器指令一样, 以#号作为一行的开始。ANSI和后来的标准都允许#号前面有空格或制表符,而且还允许在#和指令的其余部分之间有空格。但是旧版本的C要求指令从一行最左边开始,而且#和指令其余部分之间不能有空格。指令可以出现在源文件的任何地方,其定义从指令出现的地方到该文件末尾有效。我们大量使用#define指令来定义明示常量(manifest constant) (也叫做符号常量),但是该指令还有许多其他用途。程序清单16.1演示了#define指令的一些用法和属性。

预处理器指令从#开始运行,到后面的第1个换行符为止。也就是说,指令的长度仅限于一行。然而,前面提到过,在预处理开始前,编译器会把多行物理行处理为一行逻辑行。

程序16.1

/* preproc.c -- simple preprocessor examples */
#include <stdio.h>
#define TWO 2 			//可以使用注释
#define OW "Consistency is the last refuge of the unimagina\
tive. - Oscar Wilde" 	//反斜线把该定义延续到下一行

#define FOUR TWO*TWO
#define PX printf("X is %d.\n", x);
#define FMT "X is %d. \n"

int main(void){
 	int x = TWO;
 	PX;
 	x = FOUR;
 	printf(FMT, x);
 	printf("%s\n", OW);
 	printf("TWO: OW\n");
 	return 0;
} 

每行#define (逻辑行)都由3部分组成。第1部分是#define指令本身。第2部分是选定的缩写,也称为宏。有些宏代表值(如程序16.1中的例子所示),这些宏被称为类对象宏(objct-like macro)。 C语言还有类函数宏(function-like macro),稍后讨论。宏的名称中不允许有空格,而且必须遵循C变量的命名规则:只能使用字符、数字和下划线_字符,而且首字符不能是数字。第3部分(指令行的其余部分)称为替换列表或替换体(见图16.1)。一旦预处理器在程序中找到宏的示实例后,就会用替换体代替该宏(也有例外,稍后解释)。从宏变成最终替换文本的过程称为宏展开(macro expansion)。注意,可以在#define行使用标准C注释。如前所述,每条注释都会被一个空格代替。
在这里插入图片描述
程序16.1的输出如下:

X is 2.
X is 4.
Consistency is the last refuge of the unimaginative. - Oscar Wilde
TWO: OW

注意语句PX;变成了printf("X is %d.\n", x);,这里同样进行了替换。这是一个新用法,到目前为止我们只是用宏来表示明示常量。从该例中可以看.出,宏可以表示任何字符串,甚至可以表示整个C表达式。但是要注意,虽然PX是一个字符串常量,它只打印-一个名为x的变量。

下一行x = FOUR;替换的实际过程如下,首先变成x=TWO*TWO;,即是x=2*2;。宏展开到此处为止。由于编译器在编译期对所有的常量表达式(只包含常量的表达式)求值,所以预处理器不会进行实际的乘法运算,这一过程在编译时进行。预处理器不做计算,不对表达式求值,它只进行替换。

程序16.1中,可以在一行的结尾加一个反斜杠字符使该行扩展至下一行:

#define OW "Consistency is the last refuge of the unimagina\
tive. - Oscar Wilde" 

注意,第2行要与第1行左对齐,如果这样做:

#define OW "Consistency is the last refuge of the unimagina\
    tive. - Oscar Wilde" 

那么输出内容会变为:

"Consistency is the last refuge of the unimagina    tive. - Oscar Wilde" 

第2行开始到tive之间的空格也算是字符串的一部分。

C语言现在也支持const关键字,提供了更灵活的方法。用const可以创建在程序运行过程中不能改变的变量,可具有文件作用域或块作用域。另一方面,宏常量可用于指定标准数组的大小和const变量的初始值。

#define LIMIT 20
const int LIM = 50;
static int data1[LIMIT]; 	//有效
static int data2[LIM]; 		//无效
const int LIM2 = 2 * LIMIT; //有效
const int LIM3 = 2 * LIM; 	//无效

这里解释- -下上面代码中的“无效”注释。在C中,非自动数组的大小应该是整型常量表达式,这意味着表示数组大小的必须是整型常量的组合(如5)、枚举常量和sizeof表达式,不包括const声明的值(这也是C++和C的区别之一,在C++中可以把const值作为常量表达式的一- .部分)。但是,有的实现可能接受其他形式的常量表达式。例如,GCC 4.7.3不允许data2的声明,但是Clang 4.6允许。

2.1 记号

从技术角度来看,可以把宏的替换体看作是记号(token) 型字符串,而不是字符型字符串。C预处理器记号是宏定义的替换体中单独的“词”。用空白把这些词分开。例如:

#define FOUR 2*2

该宏定义有一个记号: 2*2。但是,下面的宏定义中:

#define SIX 2 * 3

有3个记号: 2*3

替换体中有多个空格时,字符型字符串和记号型字符串的处理方式不同。考虑下面的定义;

#define EIGHT 4 * 8

如果预处理器把该替换体解释为字符型字符串,将用4*8替换EIGHT。即,额外的空格是替换体的一部分。 如果预处理器把该替换体解释为记号型字符串,则用3个的记号4 * 8 (分别由单个空格分隔)来替换EIGHT。换而言之,解释为字符型字符串,把空格视为替换体的一部分;解释为记号型字符串,把空格视为替换体中各记号的分隔符。在实际应用中,–些C编译器把宏替换体视为字符串而不是记号。在比这个例子更复杂的情况下,两者的区别才有实际意义。

顺带一-提,C编译器处理记号的方式比预处理器复杂。由于编译器理解C语言的规则,所以不要求代码中用空格来分隔记号。例如,C编译器可以把2*2直接视为3个记号,因为它可以识别2是常量,*是运算符。

2.2 重定义常量

在这里插入图片描述

3 Using Arguments with #define

By using arguments, you can create function-like macros that look and act much like functions. A macro with arguments looks very similar to a function because the arguments are enclosed within parentheses. Function-like macro definitions have one or more arguments in parentheses, and these arguments then appear in the replacement portion, as shown in Figure 16.2 .

在这里插入图片描述
Here’s a sample definition:

#define SQUARE(X) X*X 

It can be used in program like this:

z = SQUARE(2); 

This looks like a function call, but it doesn’t necessarily behave identically. Listing 16.2illustrates using this and a second macro. Some of the examples also point out possible pitfalls, so
read them carefully.

Listing 16.2 The mac_arg.c Program

/* mac_arg.c -- macros with arguments */
#include <stdio.h>
#define SQUARE(X) X*X
#define PR(X) printf("The result is %d.\n", X)

int main(void){
 	int x = 5;
 	int z;
 	printf("x = %d\n", x);   //5
 	z = SQUARE(x);
 	printf("Evaluating SQUARE(x): ");
 	PR(z);  //25
 	z = SQUARE(2);
 	printf("Evaluating SQUARE(2): ");
 	PR(z);  //4
 	printf("Evaluating SQUARE(x+2): ");
 	PR(SQUARE(x+2)); //17
 	printf("Evaluating 100/SQUARE(2): ");
 	PR(100/SQUARE(2));  //100
 	printf("x is %d.\n", x);  //5
 	printf("Evaluating SQUARE(++x): ");
 	PR(SQUARE(++x));  //42
 	printf("After incrementing, x is %x.\n", x); //7
 	return 0;
} 

For SQUARE(x+2) it becomes x+2*x+2. This example pinpoints an important difference between a function call and a macro call. A function call passes the value of the argument to the function while the program is running. A macro call passes the argument token to the program before compilation; it’s a different process at a different time. Can the definition be fixed to make SQUARE(x+2) yield 49? Sure. You simply need more parentheses:

 #define SQUARE(x) (x)*(x) 

Consider the events leading to the next output line, 100/SQUARE(2) becomes 100/2*2. And SQUARE(++x) becomes ++x*++x, x gets incremented twice, once before the multiplication and once afterward:

++x*++x = 6*7 = 42 

Because the order of operations is left open, some compilers render the product 7*6. Yet other compilers might increment both terms before multiplication, yielding 7*7,or 49. Indeed, evaluating this expression results in what the standard calls undefined behavior. In all these cases, however, x starts with the value 5 and ends up with the value 7, even though the code looks as though x was incremented just once.

The simplest remedy for this problem is to avoid using ++x as a macro argument. In general, don’t use increment or decrement operators with macros. Note that ++x would work as a function argument because it would be evaluated to 6, and then the value 6 would be sent to the function.

3.1 Creating Strings from Macro Arguments: The # operator

Here’s a function-like macro:

 #define PSQR(X) printf("The square of X is %d.\n", ((X)*(X))); 

Suppose you used the macro like this:

 PSQR(8); 

Here’s the output:

The square of X is 64. 

Note that the X in the quoted string is treated as ordinary text, not as a token that can be replaced.

Suppose you do want to include the macro argument in a string. C enables you to do that. Within the replacement part of a function-like macro, the # symbol becomes a preprocessing
operator that converts tokens into strings. For example, say that x is a macro parameter, and then #x is that parameter name converted to the string "x". This process is called stringizing .
Listing 16.3illustrates how this process works.

Listing 16.3 The subst.c Program

/* subst.c -- substitute in string */
#include <stdio.h>
#define PSQR(x) printf("The square of " #x " is %d.\n",((x)*(x)))
int main(void){
 	int y = 5;
 	PSQR(y);
 	PSQR(2 + 4);
 	return 0;
}

Here’s the output:

 The square of y is 25.
 The square of 2 + 4 is 36.

In the first call to the macro, #x was replaced by "y", and in the second call #x was replaced by "2 + 4". ANSI C string concatenation then combined these strings with the other strings in the printf() statement to produce the final strings that were used. For example, the first invocation becomes this:

 printf("The square of " "y" " is %d.\n",((y)*(y))); 

Then string concatenation converts the three adjacent strings to one string:

"The square of y is %d.\n" 
3.2 Preprocessor Glue: The ## operator

Like the # operator, the ## operator can be used in the replacement section of a function-like macro. Additionally, it can be used in the replacement section of an object-like macro. The ## operator combines two tokens into a single token. For example, you could do this:

 #define XNAME(n) x ## n

Then the macro

 XNAME(4)

would expand to the following:

x4

Listing 16.4uses this and another macro using ## to do a bit of token gluing.

Listing 16.4 The glue.c Program

// glue.c -- use the ## operator
#include <stdio.h>
#define XNAME(n) x ## n
#define PRINT_XN(n) printf("x" #n " = %d\n", x ## n);

int main(void){
	int XNAME(1) = 14; // becomes int x1 = 14;
 	int XNAME(2) = 20; // becomes int x2 = 20;
 	int x3 = 30;
 	PRINT_XN(1); // becomes printf("x1 = %d\n", x1);
 	PRINT_XN(2); // becomes printf("x2 = %d\n", x2);
 	PRINT_XN(3); // becomes printf("x3 = %d\n", x3);
 	return 0;
} 

Here’s the output:

x1 = 14
x2 = 20
x3 = 30
3.3 Variadic Macros: … and VA_ARGS

Some functions, such as printf(), accept a variable number of arguments. The stdvar.h header file, discussed later in this chapter, provides tools for creating user-defined functions with a variable number of arguments. And C99/C11 does the same thing for macros. Although not used in the standard, the word variadic has come into currency to label this facility. (However, the process that has added stringizing and variadic to the C vocabulary has not yet led to labeling functions or macros with a fixed number of arguments as fixadic functions and normadic macros.)

The idea is that the final argument in an argument list for a macro definition can be ellipses
(that is, three periods). If so, the predefined macro _ _VA_ARGS_ _ can be used in the substitution part to indicate what will be substituted for the ellipses. For example, consider this
definition:

 #define PR(...) printf(_ _VA_ARGS_ _) 

Suppose you later invoke the macro like this:

PR("Howdy");
PR("weight = %d, shipping = $%.2f\n", wt, sp); 

For the first invocation, _ VA_ARGS _ expands to one argument:

"Howdy" 

For the second invocation, it expands to three arguments:

"weight = %d, shipping = $%.2f\n", wt, sp 

Thus, the resulting code is this:

printf("Howdy");
 printf("weight = %d, shipping = $%.2f\n", wt, sp);

Listing 16.5shows a slightly more ambitious example that uses string concatenation and the #
operator:

Listing 16.5 The variadic.c Program

// variadic.c -- variadic macros
#include <stdio.h>
#include <math.h>
#define PR(X, ...) printf("Message " #X ": " _ _VA_ARGS_ _)

int main(void){
 	double x = 48;
 	double y;
 	y = sqrt(x);
 	PR(1, "x = %g\n", x);
 	PR(2, "x = %.2f, y = %.4f\n", x, y);
 	return 0;
}

In the first macro call, X has the value 1, so #X becomes “1”. That makes the expansion look
like this:

 print("Message " "1" ": " "x = %g\n", x); 

Then the four strings are concatenated, reducing the call to this:

print("Message 1: x = %g\n", x); 

Here’s the output:

 Message 1: x = 48
 Message 2: x = 48.00, y = 6.9282

Don’t forget, the ellipses have to be the last macro argument:

#define WRONG(X, ..., Y) #X #_ _VA_ARGS_ _ #y // won't work 

4 Macro or Function?

待补充

在这里插入图片描述
如图16.2所示。
在这里插入图片描述
待补充 546

6.3 条件编译

在这里插入图片描述

6.3.1 #ifdef、#else和#endif指令

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
程序 16.9 ifdef.c

/* ifdef.c -- uses conditional compilation */
#include <stdio.h>
#define JUST_CHECKING
#define LIMIT 4

int main(void){
 	int i;
 	int total = 0;
 	for (i = 1; i <= LIMIT; i++){
 		total += 2*i*i + 1;
#ifdef JUST_CHECKING
 	printf("i=%d, running total = %d\n", i, total);
#endif
	}
 	printf("Grand total = %d\n", total);
 	return 0;
}

程序输出如下:

i=1, running total = 3
i=2, running total = 12
i=3, running total = 31
i=4, running total = 64
Grand total = 64 

在这里插入图片描述
待补充 556

6 其他指令

6.1 #undef指令

undef指令用于“取消”已定义的#define指令,也就是说,假如由如下定义:

#define LIMIT 400

然后,下面的指令:

#undef LIMIT

将移除上面的定义。现在就可以将LIMIT重新定义为一个新值。即使原来没有定义LIMIT,取消LIMIT的定义仍然有效。如果想使用一个名称,又不确定之前是否已经用过,为安全起见,可以用#undef指令取消该名字的定义。

6.2 从C预处理器角度看已定义

处理器在识别标识符时,遵循与C相同的规则:标识符可以由大写字母、小写字母、数字和下划线字符组成,且首字符不能是数字。当预处理器在预处理指令中发现一个标识符时,它会把该标识符当作已定义的或未定义的。这里的已定义表示由预处理器定义。如果标识符是由同一个文件中由前面的#define指令创建的宏名,而且没有用#undef指令关闭,那么该标识符是已定义的。如果标识符不是宏,假设是一个文件作用域的C变量,那么该标识符对预处理而言就是未定义的。

已定义宏可以是对象宏,包括空宏或类函数宏:

#define LIMIT 1000			//LIMIT是已定义的
#define GOOD				//GOOD是已定义的
#define A(x) ((-(X))*(X))	//A是已定义的
int q;						//q不是宏,因此是未定义的
#undef GOOD					//GOOD取消定义,是未定义的

注意,#define宏的作用域从它在文件的声明处开始,直到用#undef指令取消宏为止,或延伸至文件尾(以二者中先满足的条件作为宏作用域的结束)。另外还要注意,如果宏通过头文件引入,那么#define在文件中的位置取决于#include指令的位置。

6.3 条件编译

我们可以使用其他指令创建条件编译。也就是说,我们可以使用这些指令告诉编译器根据编译时的条件执行或忽略信息(或代码)块。

6.3.1 #ifdef、#else和#endif
#ifdef MAVIS
	#include "horse.h"		//如果已经用#define定义了MAVIS,则执行下面的指令
	#define STABLES 5
#else
	#include "cow.h"		//如果没有用#define定义MAVIS,则执行下面的指令
	#define STABLES 15
#endif

上面的代码是针对较新的编译器和ANSI标准支持的缩进格式。如果使用旧的编译器,必须左对齐所有的指令或至少左对齐#号,如下所示:

#ifdef MAVIS
#include "horse.h"		//如果已经用#define定义了MAVIS,则执行下面的指令
#define STABLES 5
#else
#include "cow.h"		//如果没有用#define定义MAVIS,则执行下面的指令
#define STABLES 15
#endif

#ifdef指令说明,如果预处理器已定义了后面的标识符(MAVIS),则执行#else或#endif指令之前的所有指令并编译所有C代码(先出现哪个指令就执行到哪里)。如果预处理器未定义MAVIS,且有#else指令,则执行#else和#endif指令之间的所有代码。

待补充 556

6.3.2 #ifndef指令

#ifndef指令与#ifdef指令的用法类似,也可以和#else、#endif一起使用,但是它们的逻辑相反。#ifndef指令判断后面的标识符是否是未定义的,常用于定义之前未定义的常量。如下所示:

/*arrays.h*/
#ifndef SIZE
	#define SIXE 100
#endif

注意旧版本可能不允许使用缩进的#define

通常,包含多个头文件时,其中的文件可能包含了相同的宏定义。#ifndef指令可以防止相同的宏被重复定义。在首次定义一个宏的头文件中用#ifndef指令激活定义,随后在其他头文件中的定义都被忽略。

#ifndef指令还有另一种用法。假设有上面的arrays.h头文件,然后把下面一行代码放入一个头文件中:

#include "arrays.h"

SIZE被定义为100。但是,如果把下面的代码放入该头文件:

#define SIZE 10
#include "arrays.h"

SIZE则被设置为10。这里,当执行到#include "arrays.h"时,由于SIZE是已定义的,所以跳过了#define SIZE 100这行代码。

#ifndef指令通常用于防止多此包含一个文件。也就是说,应该像下面这样设置头文件:

/*things.h*/
#ifndef THINGS_H_
	#define THINGS_H_
	/*省略了头文件中的其他内容*/
#endif	

假设该文件被包含了多次。当预处理器首次发现该文件被包含时,THINGS_H_是未定义的,所以定义了THINGS_H_,并接着处理该文件的其他部分。当预处理器第二次发现该文件被包含时,THINGS_H_是已定义的,所以预处理器跳过了该文件的其他部分。

为何要多此包含一个文件?最常见的原因是,许多被包含的文件中都包含着其他文件,所以显式包含的文件中可能包含着已经包含的其他文件。这有什么问题?在被包含的文件中有某些项(如,一些结构类型的声明)只能在一个文件中出现一次。C标准头文件使用#ifndef技巧避免重复包含。但是,这存在一个问题:如果确保待测试的标识符没有在别处定义。通常,实现的供应商使用这些方法解决这个问题:用文件名作为标识符、使用大写字母、用下划线字符代替文件名中的点字符、用下划线字符做前缀后后缀(可能使用两条下划线)。例如,查看stdio.h头文件,可以发现许多类似的代码:

#ifndef _STDIO_H
#define _STDIO_H
//省略了文件内容
#endif

我们也可以这样做。但是,由于标准保留使用下划线作为前缀,所以在自己的代码中不能这样写,避免与标准头文件中的宏发生冲突。程序16.10修改了程序16.6中的头文件,使用#ifndef避免文件被重复包含:

//程序16.10 names.h --修改后的names_st头文件,避免重复包含

#ifndef NAMES_H_
#define NAMES_H_

//明示常量
#define SLEN 32

//结构声明
struct names_st{
	char first[SLEN];
	char last[SLEN];
};

//类型定义
typedef struct name_st names;

//函数原型
void get_names(names *);
void show_names(const names *);
char * s_gets(char * st,int n);

#endif

用下面的程序16.11测试头文件没问题,但是如果将程序16.10中的#ifndef保护删除后,程序就无法通过编译

//程序 16.11 doubleincl.c --包含头文件两次

#include<stdio.h>
#include "names.h"
#include "names.h"

int main(){
	names winner ={ "Less", "Ismoor" };
	printf("The winner is %s %s.\n",winner.first,winner.last};
	return 0;
}
6.3.3 #if和#elif指令

13 string.h库中的memcpy和memmove

C语言不能把一个数组赋给另一个数组,所以要通过循环把数组中的每个元素赋给另一个数组相应的元素。有一个例外的情况是:使用strcpy和strncpy函数来处理字符数组。memcpy和memmove函数提供类似的方法处理任意类型的数组,下面是这两个函数的原型:

void *memcpy(void * restrict s1, const void * restrict s2, size_t n);
void *memmove(void *s1, const void *s2, size_t n); 

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
程序 16.20 mems.c

// mems.c -- using memcpy() and memmove()
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define SIZE 10

void show_array(const int ar[], int n);
// 如果编译器不支持C11的_Static_assert,那么注释掉下面这行
_Static_assert(sizeof(double) == 2 * sizeof(int), "double not twice int size");

int main(){
 	int values[SIZE] = {1,2,3,4,5,6,7,8,9,10};
 	int target[SIZE];
 	double curious[SIZE / 2] = {2.0, 2.0e5, 2.0e10, 2.0e20, 5.0e30};
 	
 	puts("memcpy() used:");
 	puts("values (original data): ");
 	show_array(values, SIZE);
 	memcpy(target, values, SIZE * sizeof(int));
 	puts("target (copy of values):");
 	show_array(target, SIZE);
 	
 	puts("\nUsing memmove() with overlapping ranges:");
 	memmove(values + 2, values, 5 * sizeof(int));
 	puts("values -- elements 0-5 copied to 2-7:");
 	show_array(values, SIZE);
 	
 	puts("\nUsing memcpy() to copy double to int:");
 	memcpy(target, curious, (SIZE / 2) * sizeof(double));
 	puts("target -- 5 doubles into 10 int positions:");
 	show_array(target, SIZE/2);
 	show_array(target + 5, SIZE/2);
 	return 0;
}

void show_array(const int ar[], int n){
 	int i;
 	for (i = 0; i < n; i++)
 	printf("%d ", ar[i]);
 	putchar('\n');
} 

下面是该程序的输出:

memcpy() used:
values (original data):
1 2 3 4 5 6 7 8 9 10
target (copy of values):
1 2 3 4 5 6 7 8 9 10

Using memmove() with overlapping ranges:
values -- elements 0-5 copied to 2-7:
1 2 1 2 3 4 5 8 9 10

Using memcpy() to copy double to int:
target -- 5 doubles into 10 int positions:
0 1073741824 0 1091070464 536870912
1108516959 2025163840 1143320349 -2012696540 1179618799 

在这里插入图片描述

12 The Assert Library

The assert library, supported by the assert.h header file, is a small one designed to help with debugging programs. It consists of a macro named assert(). It takes as its argument an integer expression. If the expression evaluates as false, the assert() macro writes an error message to the standard error stream stderr and calls the abort() function, which terminates the program. (The abort() function is prototyped in the stdlib.h header file.) The idea is to identify critical locations in a program where certain conditions should be true and to use the assert() statement to terminate the program if one of the specified conditions is not true. Typically, the argument is a relational or logical expression. If assert() does abort the program, it first displays the test that failed, the name of the file containing the test, and a line number.

12.1 Using assert

Listing 16.18 shows a short example using assert. It asserts that z is greater than or equal to 0 before attempting to take its square root. It also mistakenly subtracts a value instead of adding it, making it possible for z to obtain forbidden values.

Listing 16.18 The assert.c Program

/* assert.c -- use assert() */
#include <stdio.h>
#include <math.h>
#include <assert.h>
int main(){
 	double x, y, z;
 	
 	puts("Enter a pair of numbers (0 0 to quit): ");
 	while (scanf("%lf%lf", &x, &y) == 2 && (x != 0 || y != 0)){
 		z = x * x - y * y; /* should be + */
 		assert(z >= 0);
 		printf("answer is %f\n", sqrt(z));
 		puts("Next pair of numbers: ");
 	}
	puts("Done");
	
	return 0;
} 

Here is a sample run:

Enter a pair of numbers (0 0 to quit):
4 3
answer is 2.645751
Next pair of numbers:
5 3
answer is 4.000000
Next pair of numbers:
3 5
Assertion failed: (z >= 0), function main, file /Users/assert.c, line 14. 

The exact wording will depend on the compiler. One potentially confusing point to note is that the message is not saying that z >= 0; instead, it’s saying that the claim z >= 0 failed.

You could accomplish something similar with an if statement:

if (z < 0){
 	puts("z less than 0");
 	abort();
} 

The assert() approach has several advantages, however. It identifies the file automatically. It identifies the line number where the problem occurs automatically. Finally, there’s a mechanism for turning the assert() macro on and off without changing code. If you think you’ve eliminated the program bugs, place the macro definition

#define NDEBUG

before the location where assert.h is included and then recompile the program, and the compiler will deactivate all assert() statements in the file. If problems pop up again, you can remove the #define directive (or comment it out) and then recompile, thus reactivating all the assert() statements.

12.2 _Static_assert (C11)

待补充 762

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值