LV005-预处理简介
一、预处理的概念
预处理就是指在进行编译的第一遍扫描(词法扫描和语法分析)之前所做的工作,它由预处理程序负责完成。当编译一个程序时,系统将自动调用预处理程序对程序中的 # 开头的预处理部分进行处理,处理完毕之后才会进入程序的编译阶段。
C 语言为我们提供了多种预处理功能,如宏定义,文件包含和条件编译等。
二、预定义
1. 宏
在 C 语言源程序中,允许使用一个标识符来表示一串符号,称之为 宏,被定义为宏的标识符称为 宏名。在编译预处理的时候,对程序中出现的所有宏名,都会用宏定义中的符号串去代替,这被称为 宏替换 或者叫 宏展开。
2. 预定义符号串
2.1 有哪些?
在 C 语言中,有一些预定义的符号串,它们的值是字符串常量或者是十进制数字常量,通常是用于在调试程序时输出源程序的各项信息。
| 符号 | 常量类型 | 含义 |
| __FILE__ | 字符串 | 正在预编译编译的文件名(字符串字面值) |
| __LINE__ | 整数 | 文件的当前行号(十进制常量) |
| __DATE__ | 字符串 | 文件被编译的日期 |
| __TIME__ | 字符串 | 文件被编译的时间 |
| __STDC__ | 整数 | 如果编译器遵循 ANSI C,其值为 1,否则未定义 |
| __FUNCTION__ | 字符串 | 表示调用此预定义的函数名称 |
| __func__ | 字符串 | 表示调用此预定义的函数名称 |
2.2 使用实例
#include <stdio.h>
int main(int argc, char *argv[])
{
printf("The __FILE__ is : %s\n", __FILE__);
printf("The __LINE__ is : %d\n", __LINE__);
printf("The __DATE__ is : %s\n", __DATE__);
printf("The __TIME__ is : %s\n", __TIME__);
printf("The __STDC__ is : %d\n", __STDC__);
printf("The __FUNCTION__ is : %s\n", __FUNCTION__);
printf("The __func__ is : %s\n", __func__);
return 0;
}在终端执行以下命令:
gcc test.c -Wall # 编译程序
./a.out # 执行可执行文件然后我们会看到有如下信息输出:
The __FILE__ is : test.c
The __LINE__ is : 6
The __DATE__ is : Mar 23 2022
The __TIME__ is : 09:31:59
The __STDC__ is : 1
The __FUNCTION__ is : main
The __func__ is : main3. 宏定义
除了预定义的符号外,我们也可以自己定义宏,宏定义就是 用一个标识符来表示一个字符串,如果在后面的代码中出现了该标识符,那么就 全部替换 成指定的字符串。
【注意】
(1)这里的字符串字可以是常数、表达式、格式串等。不仅仅是代表“字符串”。
(2)如果说,“字符串”是一个含参的表达式,一定要注意,先替换,再运算。
3.1 无参宏定义
3.1.1 一般格式
无参宏定义的宏名,也就是标识符,后边不带参数,定义的一般形式是:
#define 标识符 字符串各部分说明如下:
| # | 表示这是一条预处理命令 |
| define | 宏定义命令 |
| 标识符 | 所定义的宏名(习惯上用全大写表示,但是也允许小写,看个人喜好喽),后边在程序中使用的宏都是直接使用宏名 |
| 字符串 | 可以是常数,表达式,格式串等 【说明】 字符串 与 (字符串) 似乎没有区别。 |
(1)宏定义用宏名来表示一串符号,在宏展开的时候又以该符号串取代宏名,这只是一种简单的替换,符号串中可以包含任何字符,可以是常数,也可以是表达式,预处理程序不对它做任何检查。如果有错误的话,只能在编译已被宏展开后的源程序时被发现。
(2)宏定义不是声明或者语句,行尾不必加分号( ; ),如果加上分号( ; )了,就会连分号( ; )一起替换。不过我在测试的时候是直接报错了,这里注意一下就好啦。
(3)宏定义的作用域包括从宏定义命名起到源程序结束,如果要终止其作用域,我们可以使用 #undef 来取消宏作用域。
(4)宏名引用时不要写在 " " 中,否则预处理程序不会对其进行替换。
(5)宏定义允许嵌套,在宏定义的符号串中就可以使用已经定义过的宏名,在宏展开时由预处理程序进行层层替换。
(6)可以对输出做一个宏定义,以减少编写麻烦。但是这样格式就不能自己想怎样就怎样了,不过还是要看自己需求啦。
3.1.2 使用实例 1
#include <stdio.h>
#define pi 3.1415926
#define str "Hello,world!"
#define y (x + 3)
int main(int argc, char *argv[])
{
int sum = 0;
int x = 0;
sum = 5 * y;
printf("The pi is : %f\n", pi);
printf("The str is : %s\n", str);
printf("The y is : %d\n", y);
printf("The sum is : %d\n", sum);
return 0;
}在终端执行以下命令:
gcc test.c -Wall # 编译程序
./a.out # 执行可执行文件会看到有如下信息输出:
The pi is : 3.141593
The str is : Hello,world!
The y is : 3
The sum is : 153.1.3 使用实例 2
#include <stdio.h>
#define pi 3.1415926
void fun1()
{
printf("The pi is : %f\n", pi);
}
#undef pi
void fun2()
{
printf("The pi is : %f\n", pi);
}
int main(int argc, char *argv[])
{
fun1();
fun2();
return 0;
}在终端执行以下命令:
gcc test.c -Wall # 编译程序
./a.out # 执行可执行文件其中 pi 只会在 fun1() 中生效,会直接在 fun2() 定义时报错,会看到有如下信息输出:
test.c: In function ‘fun2’:
test.c:12:32: error: ‘pi’ undeclared (first use in this function)
printf("The pi is : %f\n", pi);
^~
test.c:12:32: note: each undeclared identifier is reported only once for each function it appears in3.1.4 使用实例 3
#include <stdio.h>
#define P printf
#define D "%d\n"
#define F "%f\n"
int main(int argc, char *argv[])
{
int a = 5;
float b = 3.1415926;
P(D F, a, b);
return 0;
}在终端执行以下命令:
gcc test.c -Wall # 编译程序
./a.out # 执行可执行文件会看到有如下信息输出:
5
3.1415933.2 含参宏定义
3.2.1 一般格式
含参宏定义的宏名,也就是标识符,后边带参数,这个参数称为形参,调用的时候不仅要进行宏展开,还要传入实参。定义的一般形式是:
#define 标识符(形参表) 字符串各部分说明如下:
| # | 表示这是一条预处理命令 |
| define | 宏定义命令 |
| 标识符 | 所定义的宏名(习惯上用全大写表示,但是也允许小写,看个人喜好喽),后边在程序中使用的宏都是直接使用宏名 |
| (形参表) | 形参的列表,可以有多个形参,用逗号 "," 分隔,形参最好用 () 括起来,以免出错 |
| 字符串 | 可以是常数,表达式,格式串等 【说明】字符串 与 (字符串) 似乎没有区别,但是在含参的宏中最好用 () 括起来,减小出错的概率。 |
调用的一般形式是:
标识符(实参表);【注意】
(1)定义的时候不要带分号( ; ),调用的时候就是语句了,这就需要带上分号( ; )。
(2)注意 先替换,再运算。
(3)含参宏定义中,宏名和形参表之间不可以有空格。
#define MAX (a, b) (a > b)?a:b这种的在处理的时候直接报错了,其实按理来说,这句相当于是一个无参宏定义,宏名为 MAX ,它代表 (a, b) (a > b)?a: b 。
(4)在含参的宏定义中,形参是不会被分配内存的,所以不必做类型的定义,这是与函数不同的。在含参宏定义中,这只是 符号的替换,不存在值的传递。
(5)宏定义的形参相当于一个标识符,宏调用的实参可以是一个表达式。
3.2.2 使用实例 1
#include <stdio.h>
#define MAX(a, b) (a > b)?a:b
int main(int argc, char *argv[])
{
int a = 3;
int b = 5;
printf("MAX(a, b) is : %d\n", MAX(a, b));
return 0;
}在终端执行以下命令:
gcc test.c -Wall # 编译程序
./a.out # 执行可执行文件会看到有如下信息输出:
MAX(a, b) is : 53.2.3 使用实例 2
#include <stdio.h>
#define MAX(a, b) ((a > b)?a:b)
int main(int argc, char *argv[])
{
int a = 3;
int b = 5;
printf("MAX(a, b) is : %d\n", MAX(a + 3, b - 1));
return 0;
}在终端执行以下命令:
gcc test.c -Wall # 编译程序
./a.out # 执行可执行文件会看到有如下信息输出:
MAX(a, b) is : 63.3 宏与函数
含参的宏与函数有着很类似的形式,但是他们却有着很大的不同之处:
| 属性 | 宏 | 函数 |
| 处理阶段 | 预处理阶段,只是符号串的简单的替换 | 编译阶段 |
| 代码长度 | 每次使用宏时,宏代码都被插入到程序中。因此,除了非常小的宏之外,程序的长度都将被大幅增长 | 除了 inline 函数之外,函数代码只出现在一个地方,每次使用这个函数,都只调用那个地方的同一份代码 |
| 执行速度 | 更快 | 存在函数调用/返回的额外开销(inline 函数除外) |
| 操作符优先级 | 宏参数的求值是在所有周围表达式的上下文环境中,除非它们加上括号,否则邻近操作符的优先级可能会产生不可预料的结果 | 函数参数只在函数调用时求值一次,它的结果值传递给函数,因此,表达式的求值结果更容易预测 |
| 参数求值 | 参数每次用于宏定义时,它们都将重新求值。由于多次求值,具有副作用的参数可能会产生不可预料的结果 | 参数在函数被调用前只求值一次,在函数中多次使用参数并不会导致多种求值问题,参数的副作用不会造成任何特殊的问题 |
| 参数类型 | 宏与类型无关,只要对参数的操作是合法的,它可以使用任何参数类型 | 函数的参数与类型有关,如果参数的类型不同,就需要使用不同的函数,即使它们执行的任务是相同的 |
3.4 # 与 ##
3.4.1 # 的用途
# 的功能是将其后面的宏参数进行字符串化操作( Stringfication ),简单说就是在对它所引用的宏变量,在预编译完成替换后同时在其左右各加上一个双引号。
例如,下边的测试程序:
#include <stdio.h>
#define STR(s) #s
int main(int arc, char *argv[])
{
char a[10] = STR(mine);
int b[10] = STR(5); // 这里暂不管语法对不对只考虑到预处理
return 0;
}然后我们在终端中输入以下命令进行预编译:
gcc -E test.c -o test.i # 预编译
vim test.i然后在文件的结束,我们会看到如下几行:
# 前边的省略 ... ...
# 5 "test.c"
int main(int arc, char *argv[])
{
char a[10] = "mine";
int b[10] = "5";
return 0;
}我们会发现, mine 被替换为 "mine" 了,下面的 5 也变成了 "5"。
3.4.2 ## 的用途
后边学习过程中,遇到了一个定义:
#define __SOCKADDR_COMMON(sa_prefix) \
sa_family_t sa_prefix##family当时看的我一脸懵逼,查阅资料后,了解到,在宏定义中, ## 也就是两个 # 连用,称为连接符,主要是用于连接两个参数, ## 符会把传递过来的参数当成字符串进行替代。例如,下边的测试程序:
#include <stdio.h>
#define mine(prefix) int prefix##family
#define CONS(a,b) int(a##e##b)
int main(int arc, char *argv[])
{
mine(x_);
CONS(A, B);
return 0;
}然后我们在终端中输入以下命令进行预编译:
gcc -E test.c -o test.i # 预编译
vim test.i然后在文件的结束,我们会看到如下几行:
# 前边的省略 ... ...
# 6 "test.c"
int main(int arc, char *argv[])
{
int x_family;
int(AeB);
return 0;
}可以看到,我们传入的参数 x_在预编译后,与 fanily 连接起来了,变成了 x_family ,然后 a##e##b 变成了 AeB 。
三、文件包含
文件包含是 C 语言预处理的另一种方式,文件包含的一般形式是:
#include <filename>
/* 或者 */
#include "filename"有木有觉得很熟悉呢,这经常用于引入对应的头文件( .h 文件)。文件包含的处理过程很简单,就是 将头文件的内容插入到该语句所在行的位置,从而把头文件和当前源文件连接成一个源文件,这与复制粘贴的效果相同。
Tips:使用尖括号 < > 和双引号 " " 的区别在于 头文件的搜索路径 不同:
- 使用尖括号 < > ,编译器会到 系统路径 下查找头文件;
- 使用双引号 " " ,编译器首先在 当前目录 下查找头文件,如果没有找到,再到 系统路径 下查找。
像 stdio.h 和 stdlib.h 这些都是标准头文件,它们存放于系统路径下,所以使用尖括号和双引号都能够成功引入;而我们自己编写的头文件,一般存放于当前项目的路径下,所以不能使用尖括号,只能使用双引号。
【注意】
(1)一个 include 命令只能指定一个被包含的文件,若有多个文件要包含,则需要用多个 include 命令。而且文件的包含允许嵌套,在一个被包含的文件中还可以包含别的文件。
(2)在使用我们自己编写的头文件时,也可以在 include 中直接指明路径,例如,
#include "./include/test.h"(3)同一个头文件可以被多次引入,多次引入的效果和一次引入的效果相同,因为头文件在代码层面有 防止重复引入 的机制。
四、条件编译
当我们写的源代码是跨平台的,而代码每次只会在一个平台上运行,我们就全部编译所有代码吗?那样多少有些浪费。
其实 C 语言为我们提供了条件编译功能,以便于我们只编译需要的部分。所谓 条件编译,就是能够根据不同情况编译不同代码、产生不同目标文件的机制。条件编译的关键字为 #if 、 #ifdef 和 #ifndef 。
1. #if
1.1 使用格式
使用的时候,可以有三种格式:
1.1.1 #if
/* 1.只有 #if */
#if 常量表达式
语句块;
#endif【说明】如果常量表达式为真( 1 ),则编译语句块,若常量表达式为假( 0 ),则不做处理。
1.1.2 #if...#else
/* 2. #if ... #else... */
#if 常量表达式
语句块1;
#else
语句块2;
#endif【说明】如果常量表达式为真( 1 ),则编译语句块 1 ,若常量表达式为假( 0 ),则编译语句块 2 。
1.1.3 #if...#elif
/* 3. #if ... #elif... */
#if 常量表达式1
语句块1;
#elif 常量表达式2
语句块2;
#else 常量表达式3
语句块3;
#endif【说明】如果 常量表达式 1 的值为真(非 0 ,也就是 1 ),就对 语句块 1 进行编译;否则计算 常量表达式表达式 2 ,结果为真就对 语句块 2 进行编译;若为假就继续往下匹配,直到遇到值为真的表达式,或者遇到 #else 。
1.2 使用实例
#include <stdio.h>
#define N 1
int main(int argc, char *argv[])
{
#if N == 0
printf("#if N==0\n");
#elif N == 1
printf("#elif N==1\n");
#else
printf("#else\n");
#endif
return 0;
}在终端执行以下命令:
gcc test.c -Wall # 编译程序
./a.out # 执行可执行文件会看到有如下信息输出:
#elif N == 1【注意】 #if 命令要求判断条件为整型常量表达式,也就是说,表达式中不能包含变量,而且结果必须是整数。这是与 if 不同的地方。例如,若表达式结果为 1.3 ,将会报以下错误:
error: floating constant in preprocessor expression2. #ifdef
2.1 使用格式
使用的时候,常见的有两种格式,如下所示:
2.1.1 #ifdef
/* 1.只有 #ifdef */
#define macro
#ifdef macro
语句块;
#endif【说明】如果宏 macro 定义了,则编译语句块,若宏 macro 未定义,则不做处理。
2.1.2 #ifdef...#else
/* 2. #ifdef ... #else... */
#define macro
#ifdef macro
语句块1;
#else
语句块2;
#endif【说明】如果宏 macro 定义了,则编译语句块 1 ,若宏 macro 未定义,则编译语句块 2 。
2.2 使用实例
#include <stdio.h>
#define DEBUG1
#define DEBUG2 0
int main(int argc, char *argv[])
{
#ifdef DEBUG1
printf("DEBUG1 is define\n");
#else
printf("DEBUG1 is not define\n");
#endif
#ifdef DEBUG2
printf("DEBUG2 is define\n");
#else
printf("DEBUG2 is not define\n");
#endif
return 0;
}在终端执行以下命令:
gcc test.c -Wall # 编译程序
./a.out # 执行可执行文件会看到有如下信息输出:
DEBUG1 is define
DEBUG2 is define【注意】这种情况定义宏的话,可以有字符串替换宏名,也可以没有。
3. #ifndef
3.1 使用格式
使用的时候,一般格式如下:
3.1.1 #ifndef
/* 1.只有 #ifndef */
#ifndef macro
语句块;
#endif【说明】如果宏 macro 没有被定义,则编译语句块,若宏 macro 被定义,则不做处理。
3.1.2 #ifndef...#else
/* 2. #ifndef ... #else... */
#ifndef macro
语句块1;
#else
语句块2;
#endif【说明】如果宏 macro 没有被定义,则编译语句块 1 ,若宏 macro 被定义,则编译语句块 2 。
3.2 使用实例
#include <stdio.h>
#define DEBUG1
int main(int argc, char *argv[])
{
#ifndef DEBUG1
printf("DEBUG1 is not define\n");
#else
printf("DEBUG1 is define\n");
#endif
#ifndef DEBUG2
printf("DEBUG2 is not define\n");
#else
printf("DEBUG2 is define\n");
#endif
return 0;
}在终端执行以下命令:
gcc test.c -Wall # 编译程序
./a.out # 执行可执行文件会看到有如下信息输出:
EBUG1 is define
DEBUG2 is not define【注意】这种情况定义宏的话,可以有字符串替换宏名,也可以没有。
五、 #pragma
4.1 简介
#pragma 用于指示编译器完成一些特定的动作,它所定义的很多指示字是编译器特有的,并且在不同的编译器间是不可移植的。预处理器将会忽略它不认识的 #pragma 指令,不同的编译器可能会使用不同的方式解释同一条 #pragma 指令。
# pragma 指令应该是预处理指令中最复杂的,其用法很多。
4.2 message
4.2.1 语法格式
#pragma message("string")该参数可以在编译信息输出窗口中输出相应的信息。
4.2.2 使用实例
#include <stdio.h>
#pragma message("This is pragma message!")
int main(int argc, char *argv[])
{
printf("hello World!\n");
return 0;
}在终端执行以下命令:
gcc test.c -Wall然后,终端会有以下信息显示:
test.c:3:9: note: ‘#pragma message: This is pragma message!’
3 | #pragma message("This is pragma message!")
| ^~~~~~~4.3 once
4.3.1 语法格式
#pragma once该参数用于保证头文件只被编译一次,它 与编译器相关,不一定被编译器所支持。还记得之前我们定义头文件的时候使用的是以下形式
#ifndef __HEAD_FILENAME_H__
#define __HEAD_FILENAME_H__
/* code */
#endif它与使用 #pragma once 的区别在于前者是 C 语言所支持的,并不是只包含一次头文件,而是会包含多次,然后通过宏控制是否嵌入到源代码中,也就是说通过宏的方式,可以保证头文件里面的内容只被嵌入一次,但是由于包含了多次,预处理器还是处理了多次,所以效率上来说比较低;后者是告诉预处理器当前文件只编译一次,所以说效率较高。
如果说既想要保证移植性,又想要保证效率,我们可以两种方式同时使用:
#ifndef __HEAD_FILENAME_H__
#define __HEAD_FILENAME_H__
#pragma once
/* code */
#endif4.3.2 使用实例
- test.c
#include <stdio.h>
#include "global.h"
#include "global.h"
int main(int argc, char *argv[])
{
printf("a=%d\n", a);
return 0;
}- global.h
#pragma once
int a = 10;- 编译测试
在终端执行以下命令编译程序:
gcc test.c -Wall然后若是我们没有在 global.h 中添加 #pragma once 的话,会有以下信息产生:
In file included from test.c:3:
global.h:1:5: error: redefinition of ‘a’
1 | int a = 10;
| ^
In file included from test.c:2:
global.h:1:5: note: previous definition of ‘a’ was here
1 | int a = 10;
| ^我们发现报错了, a 出现了重复定义,我们在 global.h 中加上 #pragma once 之后,便不会再有报错。我们在终端执行 ./a, out 会有以下信息显示:
a=104.4 pack
后边在自定义数据类型的地方还会用到这个参数。
4.4.1 内存对齐
什么是内存对齐?
我使用的 64 位 Ubuntu 中, int 类型占 4 字节, char 类型占 1 字节,当他们出现在一个结构体(后边会学习到)中应该是 5 字节,但是实际上却会是 8 字节,这就是内存对齐导致的。
#include <stdio.h>
struct
{
char a;
int b;
} S1;
int main(int argc, char *argv[])
{
printf("sizeof(S1)=%ld, sizeof(S1.a)=%ld, sizeof(S1.b)=%ld\n", sizeof(S1), sizeof(S1.a), sizeof(S1.b));
return 0;
}在终端执行以下命令编译程序:
gcc test.c -Wall
./a.out然后,终端会有以下信息显示:
sizeof(S1)=8, sizeof(S1.a)=1, sizeof(S1.b)=4为什么要内存对齐?
内存是以字节为单位,但是大部分处理器并不是按字节块来存取内存的。它一般会以 2 、 4 、 6 、 8 甚至 32 字节为单位来存取内存.现在以每次存取 4 字节的处理器为例分析,取 int 类型变量( 64 位系统),该处理器只能从地址为 4 的倍数的内存开始读取数据。
假如没有内存对齐机制,数据可以任意存放,现在一个 int 变量存放在从地址 1 开始的连续 4 个字节字节地址中,该处理器去取数据时,要先从 0 地址开始读取第一个 4 字节块,并剔除不想要的字节( 0 地址), 然后从地址 4 开始读取下一个 4 字节块,同样需要删除不要的数据(也就是 5 , 6 , 7 地址处的数据), 最后留下的 2 块数据便是我们存放的 int 类型数据,这就意味着处理器要进行大量的处理,存取数据效率将会很低。
现在有了内存对齐, int 类型数据只能存放在按照对齐规则的内存中,比如说 0 地址开始的内存。那么现在该处理器在取数据时一次性就能将数据读出来了,而且不需要做额外的操作,提高了效率。内存对齐在结构体和联合体的大小计算中会得到很好的体现。
对齐规则?
编译器都有自己的默认“对齐系数”(也叫对齐模数)。 gcc 中默认 #pragma pack(4) ,可以通过预编译命令 #pragma pack(n) , n = 1,2,4,8,16 来改变这一系数。
给定值 #pragma pack(n) 和结构体中最长数据类型长度中较小的那个被称之为有效对齐值,也叫 对齐单位。
4.4.2 语法格式
#pragma pack(n) /* 设置编辑器按照 n 个字节对齐,n 可以取值 1,2,4,8,16 */
#pragma pack() /* 取消自定义字节对齐方式。 */该参数可以改变编译器默认的字节对齐方式。
4.4.2 使用实例
#include <stdio.h>
#pragma pack(1) /* 字节对齐改成了1个字节 */
/* 定义结构体数据类型 */
struct Student
{
char *name; /* 姓名 */
char gender; /* 性别 */
int age; /* 年龄 */
char id[3]; /* 学号 */
float score; /* 成绩 */
}; /* ; 不可缺少 */
#pragma pack() /* 取消自定义字节对齐 */
struct Test
{
char *name; /* 姓名 */
char gender; /* 性别 */
int age; /* 年龄 */
char id[3]; /* 学号 */
float score; /* 成绩 */
}; /* ; 不可缺少 */
int main(int argc, char *argv[])
{
struct Student stu1 = {"qidaink", 'm', 18, "01", 95.8}; /* 定义结构体变量 */
printf("sizeof(stu1) = %ld\n", sizeof(stu1));
printf("sizeof( struct Student) = %ld\n", sizeof(struct Student));
printf("sizeof(stu1.name) = %ld\n", sizeof(stu1.name));
printf("sizeof(stu1.gender) = %ld\n", sizeof(stu1.gender));
printf("sizeof(stu1.age) = %ld\n", sizeof(stu1.age));
printf("sizeof(stu1.id) = %ld\n", sizeof(stu1.id));
printf("sizeof(stu1.score) = %.ld\n\n", sizeof(stu1.score));
struct Test stu2 = {"qidaink", 'm', 18, "01", 95.8}; /* 定义结构体变量 */
printf("sizeof(stu2) = %ld\n", sizeof(stu2));
printf("sizeof( struct Student) = %ld\n", sizeof(struct Student));
printf("sizeof(stu2.name) = %ld\n", sizeof(stu2.name));
printf("sizeof(stu2.gender) = %ld\n", sizeof(stu2.gender));
printf("sizeof(stu2.age) = %ld\n", sizeof(stu2.age));
printf("sizeof(stu2.id) = %ld\n", sizeof(stu2.id));
printf("sizeof(stu2.score) = %.ld\n\n", sizeof(stu2.score));
return 0;
}在终端执行以下命令:
gcc test.c -Wall
./a.out然后,终端会有以下信息显示:
sizeof(stu1) = 20
sizeof( struct Student) = 20
sizeof(stu1.name) = 8
sizeof(stu1.gender) = 1
sizeof(stu1.age) = 4
sizeof(stu1.id) = 3
sizeof(stu1.score) = 4
sizeof(stu2) = 24
sizeof( struct Student) = 20
sizeof(stu2.name) = 8
sizeof(stu2.gender) = 1
sizeof(stu2.age) = 4
sizeof(stu2.id) = 3
sizeof(stu2.score) = 4六、 #error
1. 使用格式
#error 也是一个预处理命令,当编译器遇到 #error 的时候将停止编译,并输出自定义的消息,一般使用格式如下:
#error [自定义的错误消息]其中 [] 中的内容是可选的,也可以不输出提示信息。我们可以使用该预处理指令来停止编译,保证程序是按照我们所设想的那样进行编译的,以免产生不可预料的后果。
【注意】
(1)自定义的错误消息不需要加引号 " " ,如果加上的话,引号会被一起输出。
(2)当程序比较大时,往往有些宏定义是在外部指定的(如 makefile ),或是在系统头文件中指定的。
2. 使用实例
#include <stdio.h>
#define MACRO 1
#if MACRO == 1
#error MACRO=1
#endif
int main(int argc, char *argv[])
{
printf("hello world!\n");
return 0;
}当我们编译的时候,会有如下提示:
gcc main.c -Wall -o main
main.c:5:2: error: #error MACRO = 1
#error MACRO = 1
^
Makefile:2: recipe for target 'all' failed
make: *** [all] Error 1七、易错点
1.案例 1
1.1 题目
有以下宏定义:
#define M(x, y, z) x*y+z在主程序有以下语句:
sum = M(a + b, b + c, c + a);若 a = 1, b = 2, c = 3,则 sum 为多少?
1.2 解答
错误解答:sum = (a + b) * (b + c) + (c + a) = 3 * 5 + 4 = 19
正确解答:sum = a + b * b + c + c + a) = 1 + 2 * 2 + 3 + 3 + 1 = 12
【注意】 一定是先替换,后运算,不可直接运算。
参考资料:
C 语言 | 认识认识#pragma、#error 指令_51CTO 博客_trcv_c 指令