trevor@trevor-PC:~/linux/linux100$ ls
09 year.c
trevor@trevor-PC:~/linux/linux100$ gcc -g year.c
year.c: In function ‘main’:
year.c:6: warning: format ‘%s’ expects type ‘char *’, but argument 2 has type ‘int’
trevor@trevor-PC:~/linux/linux100$ ls
09 a.out year.c
trevor@trevor-PC:~/linux/linux100$ ./a.out
段错误
trevor@trevor-PC:~/linux/linux100$ ls
09 a.out year.c
trevor@trevor-PC:~/linux/linux100$ ulimit -c unlimited
trevor@trevor-PC:~/linux/linux100$ ./a.out
段错误 (核心已转储)
trevor@trevor-PC:~/linux/linux100$ ls
09 a.out core year.c
trevor@trevor-PC:~/linux/linux100$ gdb a.out core
GNU gdb (GDB) 7.2-ubuntu
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/trevor/linux/linux100/a.out...done.
[New Thread 6006]
warning: Can't read pathname for load map: 输入/输出错误.
Reading symbols from /lib/libc.so.6...(no debugging symbols found)...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib/ld-linux.so.2
Core was generated by `./a.out'.
Program terminated with signal 11, Segmentation fault.
#0 0x00a53b33 in vfprintf () from /lib/libc.so.6
(gdb) bt
#0 0x00a53b33 in vfprintf () from /lib/libc.so.6
#1 0x00a5aad0 in printf () from /lib/libc.so.6
#2 0x080483ea in main () at year.c:6
(gdb) quit
trevor@trevor-PC:~/linux/linux100$
如上图所示,执行了“ulimit -c unlimited”命令以后,程序再次崩溃时,提示“核心已转储”,即生成了 core 文件,执行“gdb 程序名 core文件”命令来查看程序崩溃时的相关信息,这时的 gdb 告诉我们程序最终崩溃在 vfprintf 函数,在 gdb 内再执行 bt 命令,用于查看堆栈信息,这下就一目了然了,分析得知程序由 year.c 的第 6 行进入 printf 函数,最终崩溃在 vfprintf 函数,这时我们便可以回到源代码 year.c 的第 6 行分析 printf 函数是否存在问题,这样就能很容易地发现 printf 函数中参数格式不匹配的问题。 5、多文件编译
我们在前面编译的都是单个文件,然而实际应用中的项目通常包含多个文件,下面就来演示一下多文件编译。
(1)编写 main.c 跟 add.c 两个文件,main.c 文件内调用 add.c 文件中的 add 函数。
/* main.c */
#include <stdio.h>
int main(void)
{
int a = 5;
int b = 6;
int c = 0;
c = add(a, b);
printf("%d + %d = %d \n", a, b, c);
return 0;
}
/* add.c */
int add(int a, int b)
{
return a + b;
}
(2)编译多文件
编译多文件主要有两种方法,一种是将多个文件分别编译成动态加载函数库文件,然后再将所有的动态加载函数库文件链接成一个可执行文件;一种是将多个文件直接编译生成一个可执行程序。如下图所示。
/* pow.c */
#include <stdio.h>
#include <math.h>
int main(void)
{
float a = 2;
float b = 10;
printf(" %.f^%.f = %.f \n", a, b, pow(a, b));
return 0;
}
pow 函数来自于系统数学库 libm.so,用于计算指数,去掉前缀“lib”跟后缀“.so”,剩下的 m 即为该库文件的名字。如下图所示,在编译 pow.c 时,提示 pow 函数未定义,我们加上 -l 选项执行库文件名为 m 后,pow 函数被找到,编译成功了。
trevor@trevor-PC:~/linux/linux100$ ls
pow.c
trevor@trevor-PC:~/linux/linux100$ gcc pow.c
/tmp/ccsVBC0p.o: In function `main':
pow.c:(.text+0x2d): undefined reference to `pow'
collect2: ld returned 1 exit status
trevor@trevor-PC:~/linux/linux100$ gcc pow.c -l m
trevor@trevor-PC:~/linux/linux100$ ls
a.out pow.c
trevor@trevor-PC:~/linux/linux100$ ./a.out
2^10 = 1024
trevor@trevor-PC:~/linux/linux100$ 7、综合示例
前面都是比较单一的例子,下面编写几个稍微全面些的文件将前面介绍的主要 GCC 选项一起来演示一下。
/* add.c */
int add(int a, int b)
{
return a + b;
}
/* head.h */
#ifndef _HEAD_H
#define _HEAD_H
#include <stdio.h>
#include <math.h>
int add(int a, int b);
#endif
/* main.c */
#include "head.h"
int main(void)
{
int a = 5;
int b = 6;
int c = 0;
c = add(a, b);
printf(" %d + %d = %d \n", a, b, c);
c = pow(a, b);
printf(" %d^%d = %d \n", a, b, c);
return 0;
}
编译如上源代码,附带调试信息,然后运行程序,如下图所示。
/* 99table.c */
#include <stdio.h>
int main(void)
{
int i, j; /*定义两个循环变量*/
for(i = 1, i <= 9, i++) /*控制行变量*/
{
for(j = 1, j <= i, j++) /*控制列变量*/
{
printf("%dx%d=%-2d ", i, j, i*j); /*打出数字*/
if(i == j) /*控制换行条件,就是当i=j的时候换行*/
{
printf("\n"); /*换行表达式*/
}
}
}
return 0;
}
如下图所示,当我们使用 gcc 编译 99table.c 时,提示第 7 行和第 9 行有错误,具体原因是“括号前面期望分号”。回到源代码中,第 7 行和第 9 行是 for 语句,回顾 C 语言的语法,for 语句中的三部分必须用分号分隔,而代码中用的是逗号,因而是“括号前面期望分号”,与 gcc 提示的原因一致。
trevor@trevor-PC:~/linux/linux100$ gcc 99table.c
99table.c: In function ‘main’:
99table.c:7: error: expected ‘;’ before ‘)’ token
99table.c:7: error: expected expression before ‘)’ token
99table.c:9: error: expected ‘;’ before ‘)’ token
99table.c:9: error: expected expression before ‘)’ token
trevor@trevor-PC:~/linux/linux100$
有时候类似的一个语法错误,因为连锁反应的缘故,可能导致 GCC 报告一堆错误,这时候,我们就需要保持清醒的头脑,不要被表象吓倒,按照提示一个一个地将问题解决,必要时再参考一下相关语法教程。 2、一个都不能少的头文件
编写大型程序的时候,用到的函数相当之多,我们不可能也没必要记住所有函数的头文件,使用或编译的时候查询一下函数手册,将头文件加上即可。所以,编译程序时,缺少头文件的错误也很常见。如下面这段代码,故意将 time_t 结构体所依赖的头文件 time.h 注释掉,编译看看提示什么错误。
#include <stdio.h>
//#include <time.h>
int main(void)
{
time_t now;
now = time(NULL);
printf("The time now is %s", ctime(&now));
return 0;
}
编译如上代码,gcc 显示如下图所示错误,提示 time_t 未定义,因为 time_t 包含在 time.h 中,而 time.h 又被注释掉了。
trevor@trevor-PC:~/linux/linux100$ gcc timenow.c
timenow.c: In function ‘main’:
timenow.c:6: error: ‘time_t’ undeclared (first use in this function)
timenow.c:6: error: (Each undeclared identifier is reported only once
timenow.c:6: error: for each function it appears in.)
timenow.c:6: error: expected ‘;’ before ‘now’
timenow.c:8: error: ‘now’ undeclared (first use in this function)
trevor@trevor-PC:~/linux/linux100$ trevor@trevor-PC:~/linux/linux100$ ls
pthread.c
trevor@trevor-PC:~/linux/linux100$ gcc pthread.c
/tmp/cc9Ar5ti.o: In function `main':
pthread.c:(.text+0x42): undefined reference to `pthread_create'
collect2: ld returned 1 exit status
trevor@trevor-PC:~/linux/linux100$ gcc pthread.c -lpthread
trevor@trevor-PC:~/linux/linux100$ ls
a.out pthread.c
trevor@trevor-PC:~/linux/linux100$ ./a.out
this is in the old thread!
this is in the new thread!
trevor@trevor-PC:~/linux/linux100$ 3、站在巨人的肩上,却忘了巨人的存在
这里的巨人是指函数库,我们往往在使用了函数库内的函数,在编译的时候却忘了指定要链接的函数库,这是我们在用到标准库以外的其他函数库时常常遇到的错误情况。下面就一起来再现一下遗忘巨人的过程。
如下代码在主线程中新建一个子线程,用到了 pthread 函数库中的 pthread_create 函数。
/* pthread.c */
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void *newThread(void *argv)
{
printf("this is in the new thread!\n");
return NULL;
}
int main(void)
{
pthread_t threadID;
pthread_create(&threadID, NULL, newThread, NULL);
printf("this is in the old thread!\n");
sleep(1);
return 0;
}
如下图所示,第一次编译的时候未用 -l 选项指定要链接的函数库,导致 gcc 找不到 pthread_create 函数而提示错误;第二次编译的时候虽然加上了 -l 选项,但因为函数库名错误而提示找不到函数库;第三次编译的时候指定 pthread 函数库后终于编译通过了。
trevor@trevor-PC:~/linux/linux100$ ls
pthread.c
trevor@trevor-PC:~/linux/linux100$ gcc pthread.c
/tmp/ccNtQ0RK.o: In function `main':
pthread.c:(.text+0x42): undefined reference to `pthread_create'
collect2: ld returned 1 exit status
trevor@trevor-PC:~/linux/linux100$ gcc pthread.c -lthread
/usr/bin/ld: cannot find -lthread
collect2: ld returned 1 exit status
trevor@trevor-PC:~/linux/linux100$ gcc pthread.c -lpthread
trevor@trevor-PC:~/linux/linux100$ ls
a.out pthread.c
trevor@trevor-PC:~/linux/linux100$ ./a.out
this is in the old thread!
this is in the new thread!
trevor@trevor-PC:~/linux/linux100$ 4、变量未定义而使用
这里的变量可分为局部变量、全局变量、宏变量、函数指针等,变量未定义而使用的情况很常见,具体可以分为如下几种情况:
(1)局部变量未定义
局部变量因为使用范围有限,未定义的情况很少见,多数情况下是由变量名被写错造成的;
(2)全局变量未定义
被多个文件使用的全局变量,在一个文件中定义,在其他文件中使用时需要 extern 它,全局变量未定义的情况通常是忘记 extern 这个变量造成的。
(3)宏变量未定义
宏变量通常被定义在头文件中,当我们在其他文件中使用该宏变量时,因为没有 include 它所在的头文件而造成变量未定义的错误。
(4)函数未定义
当我们使用另外一个文件中的某个函数,在编译的时候未将该文件包含进来,或者使用某个函数库中的函数,编译时却未链接该函数库,就会因为找不到该函数的定义而出错。
下面我们就来演示一下全局变量未定义的情况,编写如下两个源代码文件,特意注释掉 extern.c 文件中的 extern 语句。
/* define.c */
int num = 1024;
/* extern.c */
#include <stdio.h>
//extern int num;
int main(void)
{
printf("num = %d \n", num);
return 0;
}
编译这两个文件时,出现如下图所示错误,提示 num 变量未定义。倘若我们去掉 extern.c 文件中 extern 语句前面的注释,问题就可以迎刃而解了。
trevor@trevor-PC:~/linux/linux100$ gcc extern.c define.c
extern.c: In function ‘main’:
extern.c:6: error: ‘num’ undeclared (first use in this function)
extern.c:6: error: (Each undeclared identifier is reported only once
extern.c:6: error: for each function it appears in.)
trevor@trevor-PC:~/linux/linux100$