目录
一、实验目的
- 熟悉Linux下C语言的开发环境
- 掌握GCC和GDB的使用
- 掌握Linux下C语言程序设计与开发流程
- 掌握Makefile的基本语法和应用
- 掌握Linux文件IO编程方法;
- 掌握标准I/O和基本I/O函数的调用方法;
二、实验设备
- 硬件:PC机。
- 软件:VMware Workstation虚拟机、Linux操作系统。
三、实验内容
1、Linux 下C 语言开发流程
(1)启动虚拟机,进入Linux操作系统,然后启动终端。
(2)使用Vim 编辑源程序,在终端中输入vi hello.c,然后输入源代码,编辑完成后存盘。
(3)编译源代码,在终端下输入gcc hello.c–o hello进行编译。
(4)运行程序,在终端中查看程序运行结果。
2、GCC 编译器的使用
参考教材中gcc的命令和编译选项,自行构造实验,测试总体选项、警告和出错选项以及优化选项的编译效果。请自由选择3个编译选项进行对比试验,并描述该编译选项的效果是什么。
①测试总体选项
-E: 仅做预处理,不进行编译、汇编和链接。
-S:编译到汇编语言,不进行汇编和链接。
-c: 编译到目标代码
②测试警告选项
-w 禁止所有警告信息
③测试出错选项
-Werror:所有的警告转化为错误信息,并在警告发生时终止编译过程。
首先打开hello.c,定义void main,但是通过return 0返回一个0,测试警告信息是否转化为错误信息。
首先是不带-Werror,可以看到只报了警告。
加入- Werror 选项,可以看到打印出的警告信息变为了错误信息。
④测试优化选项
-O1:能减少目标文件大小以及执行时间。
-O2:包含-O1的优化并增加了不需要在目标文件大小和执行速度上进行优化。
可以看到使用-O1优化选项后,编译文件后大小减少,-O2选项文件大小也发生了变化。
3、GDB 基本命令的使用
使用Vim 编辑源程序,在终端中输入vi gdbtest.c,输入如下源代码,
编辑完成后存盘。此代码的功能为输出倒序main 函数中string[]数组中定义的字符串,但结果没有输出显示,现通过gdb调试的方式来解决程序中存在的问题。程序源代码如下:
#include <stdio.h>
int display1 (char *string)
int display2 (char *string1)
int main ()
{
char string[] = "Embedded Linux";
display1 (string);
display2 (string);
}
int display1 (char *string)
{
printf ("The original string is %s \n", string);
}
int display2 (char *string1)
{
char *string2;
int size,i;
size = strlen (string1);
string2 = (char *) malloc (size + 1);
for (i = 0; i < size; i++)
string2[size - i] = string1[i];
string2[size+1] = ' ';
printf("The string afterward is %s\n",string2);
}
- 用gcc 编译:gcc gdbtest.c -o gdbtest
编译是否会报错?如报错,请根据报错信息分析错误原因,修改编译错误,并把修改方法在此描述。修改编译通过后再进行后续实验。
会报错。
错误①:int display1 (char *string)和int display2 (char *string1)后面缺少分号。
修改方法:加上分号,加上头文件包含
编译通过,可以看到警告信息,提示我们包含头文件。
添加头文件:
重新编译:
(2)运行gdbtest:./ gdbtest,分析程序运行的输出结果是?
请分析程序输出结果,分析其存在的问题。
问题:倒序的xuniL deddedbmE没有输出。
(3)启动Gdb 调试:gdb gdbtest
输入gdb命令l,查看源代码:观察是否可以正常查看到源代码?如不能请分析原因是什么并回答。使用q命令,退出gdb。
不能查看,因为没有在gcc编译时加入-g选项,可执行代码中没有包含调试信息,导致GDB无法载入该可执行文件。
(4)用gcc的-g选项重新编译:gcc -g gdbtest.c -o gdbtest
再次启动gdb调试:gdb gdbtest
输入gdb命令l,查看源代码:
在20 行(for 循环处)设置断点:b 20
在23 行(printf 函数处)设置断点:b 23。
查看断点设置情况:info b
运行代码:r
单步运行代码:n
查看暂停点变量值:p string2[size - i]
查看暂停点变量值:p i
继续单步运行代码数次,并使用命令查看变量的值,发现string2[size-1]的值正确
继续程序的运行:c
程序在printf 前停止运行,此时依次查看string2[0]、string2[1]…,发现string[0]没
有被正确赋值,而后面的复制都是正确的,这时,定位程序第31 行,发现程序运
行结果错误的原因在于“size-1”。由于i 只能增到“size-1”,这样string2[0]就永远
不能被赋值而保持NULL,故输不出任何结果。
退出gdb:q
重新编辑gdbtest.c,把其中的“string2[size - i] = string1[i]”改为“string2[size – i - 1] =
string1[i];”即可。
使用Gcc 重新编译,查看运行结果:
这时,输出结果正确。
4、Make 工程管理器的使用
(1)编辑源代码,利用文本编辑器vi 创建MakeTest.c 文件,vi MakeTest.c
#include <stdio.h>
int main()
{
printf("Welcome to Test makefile!\n");
return 1;
}
(2)编写Makefile 文件。
利用文本编辑器创建一个makefile 文件,并将其保存到与MakeTest.c 相同的目录下
CC=gcc
CFLAGS=
all: MakeTest
MakeTest: MakeTest.o
$(CC) $(CFLAGS) MakeTest.o –o MakeTest
MakeTest.o: MakeTest.c
$(CC) $(CFLAGS) –c MakeTest.c –o MakeTest.o
clean:
rm *.o
- 使用Make 编译项目。执行make,查看并记录所生成的文件,对生成可执行程序进行运行,查看运行的结果。
5、文件IO编程实验
(1)基本IO(非缓冲)操作编程:编写一个基本IO操作的源代码,要求使用到基本IO操作的5个函数:打开、读取、写入、定位和关闭。编译上述源代码并调试通过,最后对运行结果予以分析验证。
首先新建了一个testIO.c文件用于测试。内容如下:
在底行写入“I am the content that has been written\n”
验证testIO.c是否真的有新添加的内容:
源代码:
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main()
{
int fd,size,offest;
char s[]="I am the content that has been written\n";
char buffer1[200],buffer2[200];
/* open */
fd = open("testIO.c",O_RDWR|O_CREAT);
if(-1 == fd) {
printf("Open or create file named \"testIO.c\"failed.\n");
return -1;
}else {
printf("Open or create file named \"testIO.c\"success!.\n\n");
}
/* read */
size = read(fd,buffer1,sizeof(buffer1));
printf("The content is:\n%s\n",buffer1);
/* lseek */
offest = lseek(fd,0,SEEK_END);
/* write */
write(fd,s,sizeof(s));
printf("The file named \"testIO.c\" has been update!\n");
/* close */
close(fd);
fd = open("testIO.c",O_RDWR|O_CREAT);
if(-1 == fd) {
printf("Open or create file named \"testIO.c\"failed.\n");
return -1;
}else {
printf("Open or create file named \"testIO.c\"success!.\n\n");
}
size = read(fd,buffer2,sizeof(buffer2));
printf("The new content is:\n%s\n",buffer2);
/* close */
close(fd);
return 0;
}
(2)标准IO(缓冲)操作编程:编写一个标准IO操作的源代码,要求使用到标准IO操作的5个函数:打开、读取、写入、定位和关闭。编译上述源代码并调试通过,最后对运行结果予以分析验证。
#include <stdio.h>
#include <stdlib.h>
struct record {
char name[10];
int age;
};
void main()
{
struct record array[2] = {{"ZhangSan",20},{"LiSi",23}};
/* fopen */
FILE *fp = fopen("testSTDIO.txt","w");
if(fp == NULL){
printf("Open file named \"testSTDIO.txt\"failed.\n");
return;
}else{
printf("Open file named \"testSTDIO.txt\"success!.\n\n");
}
/* fwrite */
fwrite(array,sizeof(struct record),2,fp);
printf("The file named \"testSTDIO.txt\" has been update!\n");
/* fclose */
fclose(fp);
/* fseek */
fseek(fp,0,SEEK_SET);
/* fopen */
fp = fopen("testSTDIO.txt","r");
if(fp == NULL){
printf("Open file named \"testSTDIO.txt\"failed.\n");
return;
}else{
printf("Open file named \"testSTDIO.txt\"success!.\n\n");
}
/* fread */
fread(array,sizeof(struct record),2,fp);
printf("The content is:\n");
printf("Name1:%s\tAge1:%d\n",array[0].name,array[0].age);
printf("Name2:%s\tAge2:%d\n",array[1].name,array[1].age);
/* fclose */
fclose(fp);
}