Top

CSDUNIXC01 DAY02

  1. Hello, World !
  2. 产生尽可能多的警告
  3. 将警告作为错误处理
  4. 分文件声明、定义和调用函数
  5. 产生错误和警告
  6. 编译器指示

1 Hello, World !

1.1 问题

使用vi编写HelloWorld.c程序,运行后,在控制台输出“Hello World!”

1.2 步骤

实现此案例需要按照如下步骤进行。

步骤一:打开vi,编辑源程序

代码如下:

#include <stdio.h>

int main()
{
	printf("Hello World!");
	return0;
}

上述代码中,以下代码:

	printf("Hello World!");

是在控制台上输出字符串“Hello World!”,其中printf是一个函数,用于将参数字符串输出到控制台上。

步骤二:预编译

在控制台使用gcc -E HelloWorld.c -o HelloWorld.i命令进行编译预处理,完成编译前的预处理工作,将源文件HelloWorld.c处理成HelloWorld.i文件。

步骤三:编译

在控制台使用gcc -S HelloWorld.i命令将与处理后的高级语言文件HelloWorld.i翻译成汇编语言,得到汇编文件HelloWorld.s。

步骤四:汇编

在控制台使用gcc -c HelloWorld.s命令将汇编文件HelloWorld.s翻译成机器指令,得到目标文件HelloWorld.o。

步骤五:链接

在控制台使用gcc HelloWorld.o -o HelloWorld命令将目标文件HelloWorld.o和标准库链接,得到可执行程序HelloWorld。

步骤五:运行

在控制台敲入可执行程序名HelloWorld,然后回车即可执行该文件。

1.3 完整代码

本案例的完整代码如下所示:

#include <stdio.h>

int main()
{
	printf("Hello World!");
	return0;
}

2 产生尽可能多的警告

2.1 问题

gcc是gnu旗舰产品,它的功能非常强大,有多达上千个选项用于完成各种功能。本案例介绍如何产生尽可能多的警告。

2.2 步骤

实现此案例需要按照如下步骤进行。

步骤一:使用Wall选项产生尽可能多的警告

在控制台使用gcc -WallHelloWorld.c命令一次性编译链接源文件HelloWorld.c时,如果源文件有问题,则会产生尽可能多的警告,以提示程序的问题所在。

2.3 完整代码

本案例没有代码。

3 将警告作为错误处理

3.1 问题

gcc是gnu旗舰产品,它的功能非常强大,有多达上千个选项用于完成各种功能。本案例介绍如何将警告作为错误处理。

3.2 步骤

实现此案例需要按照如下步骤进行。

步骤一:使用Werror选项将警告作为错误处理

在控制台使用gcc -Werror HelloWorld.c命令一次性编译链接源文件HelloWorld.c时,如果源文件有警告,建会按照错误处理。

3.3 完整代码

本案例没有代码。

4 分文件声明、定义和调用函数

4.1 问题

当需要编写较大型的程序时,往往把与这方面相关的函数集中到某一个文件中,把那方面相关的函数集中到另一个文件中。这时就需要将函数的声明放在扩展名为.h的头文件中,将函数的定义放在扩展名为.c的文件中,而在其他的扩展名为.c的文件中调用它们。

4.2 步骤

实现此案例需要按照如下步骤进行。

步骤一:定义头文件print.h

代码如下所示:

#ifndef exam_print_h
#define exam_print_h

void printArea(char* str, double area);

#endif

上述代码中,以下代码:

#ifndef exam_print_h
#define exam_print_h

和代码:

#endif

在一起的作用是防止该头文件中其间的内容被多次包含,而造成的重复定义错误。

上述代码中,以下代码:

void printArea(char* str, double area);

在头文件中声明了一个函数。

步骤二:在print.c文件中定义头文件print.h中声明的函数体

代码如下所示:

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

void printArea(char* str, double area)
{
printf("图形%s的面积是%lf\n", str, area);
}

注意:需要使用#include将头文件包含进来。

上述代码中,以下代码:

void printArea(char* str, double area)
{
printf("图形%s的面积是%lf\n", str, area);
}

是头文件print.h中声明的函数的定义。

步骤三:在main.c文件中调用头文件print.h中声明的函数

代码如下所示:

#include "print.h"

int main(int argc, constchar * argv[])
{

// insert code here...
printArea("圆", 100);
return0;
}

注意:需要使用#include将头文件包含进来。

上述代码中,以下代码:

printArea("圆", 100);

是头文件print.h中声明的函数的调用。

步骤四:编译链接

在控制台使用gcc main.c print.c –I <指定路径>命令一次性编译链接源文件main.c和print.c,由于没有指定生成的文件名,所以生成可执行文件a.out。

上述命令中,选项-I的作用是先找-I指定的目录,再找当前目录,最后找系统目录。

4.3 完整代码

本案例中的完整代码如下所示:

文件print.h

#ifndef exam_print_h
#define exam_print_h

void printArea(char* str, double area);

#endif

文件print.c

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

void printArea(char* str, double area)
{
printf("图形%s的面积是%lf\n", str, area);
}

文件main.c

#include "print.h"

int main(int argc, constchar * argv[])
{

// insert code here...
printArea("圆", 100);
return0;
}

5 产生错误和警告

5.1 问题

C语言提供了许多预处理命令,预处理器的主要作用就是把通过预处理的内建功能对一个资源进行等价替换,最常见的预处理有:文件包含,条件编译、布局控制和宏替换4种。

本案例介绍产生错误或警告的预处理命令。

5.2 步骤

实现此案例需要按照如下步骤进行。

步骤一:产生错误或警告的预处理命令

代码如下所示:

#include <stdio.h>

#define VER 7

#if VER <3
	#error "版本太低"
#elif VER >6
	#warning "版本太高"
#else
int main()
{
	printf("正常运行");
	return0;
}
#endif

上述代码中,以下代码:

#if VER <3
	#error "版本太低"

当宏名VER的值小于3的时候,产生预编译错误"版本太低"

上述代码中,以下代码:

#elif VER >6
	#warning "版本太高"

当宏名VER的值大于6的时候,产生预编译警告"版本太高"

上述代码中,以下代码:

#else
int main()
{
	printf("正常运行");
	return0;
}

只有宏名VER的值在3到5之间时,才正确编译代码。

注意:以下代码一定不要忘记。

#endif

5.3 完整代码

本案例中的完整代码如下所示:

#include <stdio.h>

#define VER 7

#if VER <3
	#error "版本太低"
#elif VER >6
	#warning "版本太高"
#else
int main()
{
	printf("正常运行");
	return0;
}
#endif

6 编译器指示

6.1 问题

C语言提供了许多预处理命令,预处理器的主要作用就是把通过预处理的内建功能对一个资源进行等价替换,最常见的预处理有:文件包含,条件编译、布局控制和宏替换4种。

本案例介绍编译器指示预处理命令。

6.2 步骤

实现此案例需要按照如下步骤进行。

步骤一:依赖警告

代码如下所示:

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

#pragma GCC dependency "dep.c"

int main()
{
	printf("依赖警告\n");

	return0;
}

上述代码中,以下代码:

#pragma GCC dependency "dep.c"

在这种情况下,即如果当前文件被修改了,则文件dep.c也必须修改,使用该语句。此时,编译器如果发现文件dep.c的修改时间比当前文件的修改时间早,即当前文件修改了,但文件dep.c没有修改,则发出警告信息。

文件dep.h,代码如下:

#ifndef dep_h
#define dep_h

void print();

#endif

文件dep.c,代码如下:

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

void print()
{
	printf("被依赖的文件\n");
}

步骤二:毒药报错

代码如下所示:

#include <stdio.h>

#pragma GCC poison goto

int main()
{
loop:	printf("毒药报错");
	goto loop;

	return0;
}

上述代码中,以下代码:

#pragma GCC poison goto

通知编译器,如果遇到标识符goto,就报错

所以当编译到下述语句时:

	goto loop;

会报错。

步骤三:指定字符对齐方式

代码如下所示:

#include <stdio.h>

#pragma pack(push) 
#pragma pack(4)
struct test
{
char m1;
double m4;
int m3;
};
#pragma pack(pop)

int main()
{
	printf("%ld\n", sizeof(struct test));

	return0;
}

上述代码中,以下代码:

#pragma pack(push) 

保存当前的字符对齐方式状态。

上述代码中,以下代码:

#pragma pack(4)

设定字符对齐状态为4字节。

上述代码中,以下代码:

#pragma pack(pop)

恢复字符对齐方式状态。

上述代码中,以下代码:

	printf("%ld\n", sizeof(struct test));

以上结构体的大小为16,下面分析其存储情况,首先为m1分配空间,其偏移量为0,满足我们自己设定的对齐方式(4字节对齐),m1大小为1个字节。接着开始为m4分配空间,这时其偏移量为1,需要补足3个字节,这样使偏移量满足为n=4的倍数(因为sizeof(double)大于4),m4占用8个字节。接着为m3分配空间,这时其偏移量为12,满足为4的倍数,m3占用4个字节。这时已经为所有成员变量分配了空间,共分配了16个字节,满足为n的倍数。如果把上面的#pragma pack(4)改为#pragma pack(8),那么我们可以得到结构的大小为24。

6.3 完整代码

本案例中的完整代码如下所示:

#include <stdio.h>

#pragma pack(push) 
#pragma pack(4)
struct test
{
char m1;
double m4;
int m3;
};
#pragma pack(pop)

int main()
{
	printf("%ld\n", sizeof(struct test));

	return0;
}