对于.c格式的C文件,可以采用gcc或g++编译
对于 .cc、.cpp格式的C++文件,应该采用g++进行编译
常用的选项两种
-c 表示编译源文件
-o 表示输出目标文件
-g 在目标文件中产生调试信息,用于gdb调试
-D <宏定义> 编译时将宏定义传入进去
-Wall
选项可以打开所有类型的语法警告,以便帮助我们确定代码是正确的,并且尽可能实现可移植性。
例如有两个文件main.cpp,func.cpp
其中
main.cpp
内容为:
#include <stdio.h>
#include <stdlib.h>
int MyFunc();
int main()
{
#ifdef _DEBUG
printf("Debug MyFunc is:%d/n",MyFunc());
#else
printf("NDEBUG MyFunc is:%d/n",MyFunc());
#endif
}
func.cpp
内容为:
int MyFunc()
{
return 123;
}
编译和连接的方式有如下一些 :
1、 g++ -c func.cpp
将编译func.cpp,并且生成同名的但扩展名为.o的二进制目标文件 func.o
同样道理
g++ -c main.cpp
将编译main.cpp,并且生成同名的但扩展名为.o的二进制目标文件 main.o
2、g++ -c func.cpp -o func.o
功能同(1)一样,但是显式地指定了输出文件名为main.o
同样道理
g++ -c main.cpp –o main.o
编译main.cpp,并输出目标文件main.o
3、 (1)、(2)的基础上
l g++ main.o func.o
l g++ -o a.out main.o func.o
l g++ -o a.out *.o
都将连接目标文件main.o和func.o最后形成可执行文件a.out
对于第一种,如果没有显式指定可执行文件名,g++默认为a.out
4、 也可以将编译和链接的过程合为一块处理:
g++ *.cpp
g++ func.cpp main.cpp
g++ -o a.out func.cpp main.cpp
都将先编译指定的源文件,如果成功的话,再链接成可执行文件a.out
采用第4种方式,在其中第某个源文件中必须有main函数,否则链接通不过。
5、 如果希望在编译时传入宏定义,可使用-D参数,例如
g++ -D _DEBUG *.cpp
makefile的工作过程为:先将需要编译连接的c/c++源文件组织到文件makefile中,
接着运行make程序,make程序读取当前文件夹下面的makefile文件信息,并根据makefile
里面的组织信息,调用相应的gcc/g++/shell等程序,完成对源文件的批量编译和连接。
要写makefile文件,首页必须清楚makefile中的目标文件和依赖文件的概念。
通常情况下,目标文件和依赖文件都是指实际的文件。
例如,有makefile文件,内容如下:
main.exe:main.o func.o
g++ -o main.exe main.o func.o
main.o:main.cpp
g++ -c main.cpp
func.o:func.cpp
g++ -c func.cpp
|
文件第一行中的文件main.exe称为目标文件,冒号后面以空格分隔的两个文件
称为main.exe的依赖文件。意思是文件main.exe的产生依赖于文件main.o和
func.o
同样道理:
第3行的main.o为目标文件,main.cpp为main.o的依赖文件
第5行的func.o为目标文件,func.cpp为func.o的依赖文件.
文件第2行(以tab开头)表示要产生第1行的目标文件需要执行的命令。
对于该makefile文件,程序make处理过程如下:
1、
make
程序首先读到第1行的目标文件main.exe和它的两个依赖文件main.o和func.o;然后比较文件main.exe和main.o/func.o的产生时间,如果main.exe比main.o/func.o旧的话,则执行第2条命令,以产生目标文件main.exe,否则make立即返回,表示目标文件不必依赖文件旧,不需要更新。
2、
在执行第2行的命令前,它首先会查看makefile中的其他定义,看有没有以第1行main.o和func.o为目标文件的依赖文件,如果有的话,继续按照(1)、(2)的方式匹配下去。
3、
根据(2)的匹配过程,make程序发现第3行有目标文件main.o依赖于main.cpp,则比较目main.o与它的依赖文件main.cpp的文件新旧,如果main.o比main.cpp旧,则执行第4行的命令以产生目标文件main.o.在执行第4条命令时,main.cpp在文件makefile不再有依赖文件第定义,make程序不再继续往下匹配,而是执行第4条命令,产生目标文件main.o
4、
目标文件func.o按照上面的同样方式判断产生.
执行(3)、(4)产生完main.o和func.o以后,则第2行的命令可以顺利地执行了,最终产生了第1行的目标文件main.exe。
先看一些makefile的特殊情况:
makefile文件内容为
a:
echo 'a'
|
执行make时
如果文件a存在,echo ‘a’将不会被调用,可以理解为文件a没有依赖文件,则认为文件a总是最新的,不需要执行;
如果文件a不存在,echo ‘a’将会被调用。
a:b
echo 'a'
|
如果文件b不存在,不管a是否存在,make时都将会报错
如果文件b存在,文件a不存在,则echo ‘a’会被调用
如果文件a、b都存在,则按照正常的方式先比较文件新旧,决定是否调用echo
a:b
echo 'a'
|
a:
echo 'a'
b:
echo ‘b’
|
make时,如果文件a不存在,echo ‘a’将会被调用,但是echo’b’ 不会被调用,说明make执行的入口点只有一个,就是第一个目标文件的定义,其它的调用都是由于有与第一条定义直接或间接依赖关系而被调用的;
make b时,表示指定入口点为b,则echo ‘b’被调用,echo ‘a’不被调用,也说明入口点只有一个
a:
echo 'a'
b:a
echo ‘b’
|
make b时,调用顺序为echo ‘a’然后是echo ‘b’,说明依赖体的匹配过程是全文件匹配,并不是顺序往下的。
伪目标:再回到(2)中的第一种情况,如果希望指定入口点为b,并且不管文件b是否都存在,都调用echo ‘b’,则可以定义b为伪目标:
a:
echo 'a'
.PHONY: b
b:
echo ‘b’
|
.PHONY是makefile文件的关键字,表示它后面列表中的目标均为伪目标,这样,不论文件b是否存在,执行make b时,echo ‘b’都将被调用。
伪目标通常用在清理文件、强制重新编译等情况下,例如:
main.exe:main.o func.o
g++ -o main.exe main.o func.o
main.o:main.cpp
g++ -c main.cpp
func.o:func.cpp
g++ -c func.cpp
.PHONY:rebuild clean
rebuild:clean main.exe
clean:
rm *.o *.exe
|
执行make clean将清除掉文件夹中的二进制可执行文件
执行make rebuild则先执行清除,再重新编译连接。
随着软件项目的变大、变复杂,源文件也越来越多,如果采用前面的方式写makefile文件,将会使makefile也变得复杂而难于维护。通过make支持的变量定义、使用、内置函数和规则,可以写出通用性较强的makefile文件,使得同一个makefile文件能够适应不能的项目。
变量:为一文本串定义一个名字,名字即为变量的名称,文本串即为变量的值。
定义变量的一般方法:
变量名=变量值
递规变量展开(几个变量共享一个值)
或者
变量名:=变量值
简单变量展开(类似于C++的赋值)
使用变量的一般方法:
$(变量名)=???
赋值
???=$(变量名)
引用
例:
OBJS= main.o func.o
main.exe:$(OBJS)
g++ -o main.exe main.o func.o
main.o:main.cpp
g++ -c main.cpp
func.o:func.cpp
g++ -c func.cpp
|
make内部事先定义好了一些变量,他们分为两种类型,自动变量和预定义变量:
自动变量: 指在使用的时候,自动用特定的值替换。
常用的有:
变量
|
说明
|
$@
|
当前规则的目标文件
|
$<
|
当前规则的第一个依赖文件
|
$^
|
当前规则的所有依赖文件,以逗号分隔
|
$?
|
规则中日期新于目标文件的所有相关文件列表,逗号分隔
|
$(@D)
|
目标文件的目录名部分
|
$(@F)
|
目标文件的文件名部分
|
预定义变量:也是make内部事先定义好的变量,但是它的值是固定的,并且有些的值是为空的。
常用的有:
变量
|
说明
|
$(CC)
|
C编译程序,默认值:cc
|
$(CPP)
|
C预处理程序,默认值:cpp
|
$(RM)
|
文件删除程序,默认值:”rm –f”
|
$(CPPFLAGS)
|
传给C预处理程序第标志,没有默认值
|
$(CFLAGS)
|
传给C编译器第标志,没有默认值
|
根据内部变量,可以将makefile改写为:
OBJS :=main.o func.o
CC :=g++
main.exe:$(OBJS)
$(CC) -o $@ $^
main.o:main.cpp
$(CC) -o $@ -c $^
func.o:func.cpp
$(CC) -o $@ -c $^
|
模式规则
:
模式规则是指通用第匹配方式,模式规则必须指定”%”,百分号可以匹配任何字符串,
例如下面规则:
%.o: %.cpp
表示任何目标文件的依赖文件是与目标文件同名的并且扩展名为.cpp的文件
根据模式规则,上面的makefile可改写为:
OBJS :=main.o func.o
CC :=g++
main.exe:$(OBJS)
$(CC) -o $@ $^
%.o: %.cpp
$(CC) -o $@ -c $^
|
函数:
makefile 里的函数跟它的变量很相似——使用的时候,你用一个 $ 符号跟开括号,函数名,空格后跟一列由逗号分隔的参数(第一个参数前不要逗号),最后用关括号结束。例如,有一个叫wildcard 的函 数,它有一个参数,功能是搜索当前目录下的文件名,展开成一列所有符合由其参数描述的文件名,文件间以空格间隔。你可以像下面所示使用这个命令:
SOURCES = $(wildcard *.cpp *.o)
这行会产生一个所有以 '.cpp'和’.o’ 结尾的文件的列表,然后存入变量 SOURCES 里。当然你不需要一定要把结果存入一个变量。
另一个有用的函数是字符串替换函数:
SOURCES = $(wildcard *.cpp *.o)
这行会产生一个所有以 '.cpp'和’.o’ 结尾的文件的列表,然后存入变量 SOURCES 里。当然你不需要一定要把结果存入一个变量。
另一个有用的函数是字符串替换函数:
$(patsubst,要查找的子串,替换后的目标子串,源字符串)
它用于将源字符串(以空格分隔)中的所有要查找的子串替换成目标子串。
例如,处理那个经过上面定义后的变量,
OBJS = $(patsubst %.cpp,%.o,$(SOURCES))
将处理所有在SOURCES 字列中的字(一列文件名),如果它的结尾是 '.cpp' ,就用'.o' 把 '.cpp' 替换。
注意这里的 % 符号将匹配一个或多个字符。
$(addprefix 前缀,源字符串)函数把第二个参数列表的每一项前缀上第一个参数值
下面是一个较为通用的makefile:
DIR := ./debug
EXE := $(DIR)/Main.exe
CC := g++
LIBS :=
SRCS := $(wildcard *.cpp) $(wildcard *.c) $(wildcard *.cc)
OCPP := $(patsubst %.cpp, $(DIR)/%.o, $(wildcard *.cpp))
OC := $(patsubst %.c, $(DIR)/%.co, $(wildcard *.c))
OCC := $(patsubst %.cc, $(DIR)/%.cco, $(wildcard *.cc))
OBJS := $(OC) $(OCC) $(OCPP)
RM := rm -rf
CXXFLAGS := -Wall -g
start : mkdebug $(EXE)
mkdebug :
@if [ ! -d $(DIR) ]; then mkdir $(DIR); fi;
$(EXE) : $(OBJS)
$(CC) -o $@ $(OBJS) $(addprefix -l,$(LIBS))
$(DIR)/%.o : %.cpp
$(CC) -c $(CXXFLAGS) $< -o $@
$(DIR)/%.co : %.c
$(CC) -c $(CXXFLAGS) $< -o $@
$(DIR)/%.cco : %.cc
$(CC) -c $(CXXFLAGS) $< -o $@
.PHONY : clean rebuild
clean :
@$(RM) $(DIR)
rebuild: clean start
|
Linux
包含了一个叫
gdb
的调试程序。
gdb
可以用来调试
C
和
C++
程序。
gdb
提供如下一些常用功能:
l
它使你能监视你程序中变量的值
.
l
它使你能设置断点以使程序在指定的代码行上停止执行
.
l
它使你能一行行的执行你的代码
.
为了使
gdb
正常工作
,
你必须使你的程序在编译时包含调试信息
.
调试信息包含你程序里的每个变量的类型和在可执行文件里的地址映射以及源代码的行号。
gdb
利用这些信息使源代码和机器码相关联。
在程序编译时用
-g
选项可打开调试选项
.
gdb
基本命令
gdb 支持很多的命令使你能实现不同的功能 . 这些命令从简单的文件装入到允许你检查所调用的堆栈内容的复杂命令 , 表 1 列出了你在用 gdb 调试时会用到的一些命令 .
gdb 支持很多的命令使你能实现不同的功能 . 这些命令从简单的文件装入到允许你检查所调用的堆栈内容的复杂命令 , 表 1 列出了你在用 gdb 调试时会用到的一些命令 .
表 1. 基本 gdb 命令 .
命 令
|
描 述
|
file
|
装入想要调试的可执行文件
|
kill
|
终止正在调试的程序
|
list
|
列出产生执行文件的源代码的一部分
例:
l
l 3
l 5,9
l func.cpp:2,5
|
next
|
执行一行源代码但不进入函数内部
|
step
|
执行一行源代码而且进入函数内部
|
run
|
执行当前被调试的程序
|
quit
|
终止
gdb
|
watch
|
使你能监视一个变量的值而不管它何时被改变
|
break
|
在代码里设置断点
, 这将使程序执行到这里时被挂起
例如:
|
make
|
使你能不退出
gdb 就可以重新产生可执行文件
|
shell
|
使你能不离开
gdb 就执行 UNIX shell 命令
|
whatis
|
显示变量的简单描述
|
ptype
|
显示变量的定义
|
backtrace(where)
|
显示函数调用堆栈
|
print
|
打印变量
.如 p /x szBuf[0]@10表示以16进制方式打印从szBuf[0]开始的是个元素
|
up
|
进入上函数调用堆栈的上一级
|
down
|
进入下函数调用堆栈的下以及
|
set variable
|
临时定义一个变量语法为:
set variable varname=value
|
gdb
支持很多与
Linux Shell
一样的命令编辑特征
.
你能象在
bash
或
tcsh
里那样按
Tab
键让
gdb
帮你补齐一个惟一的命令
,
如果不惟一的话
gdb
会列出所有匹配的命令
.
你也能用光标键上下翻动历史命令
.
下面列出了将被调试的程序
.
这个程序有两个源文件
main.cpp
和
func.cpp ,
它显示一个简单的问候
,
再用反序将它列出
.
main.cpp:
void MyPrint(const char *pszSrc);
void MyPrint2(const char *pszSrc);
int main ()
{
char szSrc[] = "hello there";
MyPrint(szSrc);
MyPrint2(szSrc);
}
|
func.cpp
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void MyPrint(const char *pszSrc)
{
printf("The string is %s/n", pszSrc);
}
void MyPrint2(const char *pszSrc)
{
char *pszRev;
int i,iLen;
iLen=strlen(pszSrc);
pszRev=(char *)malloc(iLen+1);
for(i=0;i<iLen;i++)
pszRev[iLen-i]=pszSrc[i];
pszRev[iLen]='/0';
printf("The revert string is:%s/n",pszRev);
free(pszRev);
}
|
用下面的命令编译它
(
注意加上
-g
的编译选项
):
g++ -g *.cpp
g++ -g *.cpp
这个程序执行时显示如下结果
:
The string is hello there
The revert string is:
输出的第一行是正确的
,
但第二行打印出的东西并不是我们所期望的
.
我们所设想的输出应该是
:
The string printed backward is ereht olleh
由于某些原因
, MyPrint2
函数没有正常工作
.
让我们用
gdb
看看问题究竟出在哪儿
,
先键入如下命令
:
gdb a.out
注意 : 记得在编译 a.out 程序时把调试选项打开 .
如果你在输入命令时忘了把要调试的程序作为参数传给
gdb ,
你可以在
gdb
提示符下用
file
命令来载入它
:
(gdb) file a.out
这个命令将载入 a.out 可执行文件就象你在 gdb 命令行里装入它一样 .
这时你能用 gdb 的 run 命令来运行 a.out 了 . 当它在 gdb 里被运行后结果大约会象这样 :
(gdb) file a.out
这个命令将载入 a.out 可执行文件就象你在 gdb 命令行里装入它一样 .
这时你能用 gdb 的 run 命令来运行 a.out 了 . 当它在 gdb 里被运行后结果大约会象这样 :
(gdb) file a.out
Reading symbols from a.out...done.
(gdb) run
Starting program: /home/ghaha/projects/testgdb/a.out
The string is hello there
The revert string is:
Program exited normally.
|
这个输出和在
gdb
外面运行的结果一样
.
问题是
,
为什么反序打印没有工作
?
为了找出症结所在
,
我们可以在
MyPrint2
函数的
for
语句后设一个断点
,
具体的做法是在
gdb
提示符下键入
list
命令三次
,
列出源代码
:
(gdb) l[ist] func.cpp:1
(gdb) l[ist]
(gdb) l[ist] func.cpp:1
(gdb) l[ist]
技巧 : 在 gdb 提示符下按回车健将重复上一个命令 .
第一次键入 l[ist] func.cpp:1 命令的输出如下 :
(gdb) l func.cpp:1
1
#include <stdlib.h>
2
#include <stdio.h>
3
#include <string.h>
4
5
void MyPrint(const char *pszSrc)
6
{
7
printf("The string is %s/n", pszSrc);
8
}
9
10
void MyPrint2(const char *pszSrc)
|
如果按下回车
, gdb
将再执行一次
l[ist]
命令
,
给出下列输出
:
(gdb) l
11
{
12
char *pszRev;
13
int i,iLen;
14
iLen=strlen(pszSrc);
15
pszRev=(char *)malloc(iLen+1);
16
for(i=0;i<iLen;i++)
17
pszRev[iLen-i]=pszSrc[i];
18
pszRev[iLen]='/0';
19
printf("The revert string is:%s/n",pszRev);
20
free(pszRev);20
|
根据列出的源程序
,
你能看到要设断点的地方在第
24
行
,
在
gdb
命令行提示符下键入如下命令设置断点
:
(gdb) break 17
gdb 将作出如下的响应 :
(gdb) break 17
gdb 将作出如下的响应 :
(gdb) b 17
Breakpoint 1 at 0x80484fb: file func.cpp, line 17.
|
现在再键入
run
命令
,
将产生如下的输出
:
(gdb) run
Starting program: /home/ghaha/projects/testgdb/a.out
The string is hello there
Breakpoint 1, MyPrint2(char const*) (pszSrc=0xbfffe500 "hello there")
at func.cpp:17
17
pszRev[iLen-i]=pszSrc[i];
|
你能通过设置一个观察
pszRev[iLen - i]
变量的值的观察点来看出错误是怎样产生的
,
做法是键入
: (gdb) watch pszRev[iLen-i]
gdb
将作出如下回应
:
Watchpoint 2: string2[iLen - i]
现在可以用 n[ext] 命令来一步步的执行 for 循环了 :
(gdb) n
经过第一次循环后 , gdb 告诉我们 pszRev[iLen - i] 的值是 `h`. gdb 用如下的显示来告诉你这个信息 :
Watchpoint 2: string2[iLen - i]
现在可以用 n[ext] 命令来一步步的执行 for 循环了 :
(gdb) n
经过第一次循环后 , gdb 告诉我们 pszRev[iLen - i] 的值是 `h`. gdb 用如下的显示来告诉你这个信息 :
(gdb) n
Watchpoint 2: pszRev[iLen - i]
Old value = 0 '/0'
New value = 104 'h'
MyPrint2(char const*) (pszSrc=0xbfffe500 "hello there")
at func.cpp:16
16
for(i=0;i<iLen;i++)
|
这个值正是期望的
.
后来的数次循环的结果都是正确的
.
此时如果想一次运行到循环退出时,可以这么打断点:
首先取消查看和取消原来所有断点:
(gdb) info b [
查看所有断点信息
]
(gdb) d b
Delete all breakpoints? (y or n) y [
删除原来所有断点
]
(gdb) b 17 if i>=iLen-1 [
只有在
i>=iLen-1
时才停止在
17
行
]
(gdb) continue [
继续全速运行
]
Continuing.
Breakpoint 2, MyPrint2(char const*) (pszSrc=0xbfffde00 "hello there")
at func.cpp:17
17 pszRev[iLen-i]=pszSrc[i];
(gdb) p i
$1 = 10
(gdb) p iLen
$2 = 11
这时可以看到iLen-i已经小于0,已经越界了。并且实际上pszRev[0]赋的值就是为0,所以导致
pszRev的结果就是一个空字符串。
3.3. assert断言宏
宏assert的原型定义在头文件<assert.h>中,其作用时如果它的测试条件为假,则终止程序的运行。
原型定义如下:
#include <assert.h>
void assert(int iCondition);
assert用在调试版本中,如果在源文件中定义了DEBUG,assert宏和它里面的参数表达式都将不会执行
例如
#define NDEBUG
#include <assert.h>
int main()
{
assert(0); //将不会被执行
return 0;
}
或者在编译的时候,加入-D NDEBUG选项,也可以使assert无效。
3.4. 错误处理函数和进程退出函数
错误号全局变量errno
通常errno为0,表示没有错误,Linux系统调用和许多库函数在出错时会将errno置为一个非0的值。为了保证判断错误号不受干扰,最后在调用可能出错的函数前将全局变量errno置0。
例:
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <errno.h>
#include <string.h>
int main(int argc,char *argv[])
{
errno=0;
int iSqrt=sqrt((double)atoi(argv[1]));
if(errno)
printf("sqrt error/n");
else
printf("result is:%d/n",iSqrt);
return 0;
}
|
strerror
函数和perror函数
strerror根据错误号返回错误的描述,
原型:
#include <string.h>
char *strerror(int iErrCode)
perror根据全局变量errno和你传入的字符串,打印一条出错信息,
原型:
#include <stdio.h>
#include <errno.h>
void perror(const char *pszInfo);
例如:
perror(“The error info”);
等价于
printf(“The error info:%s/n”,strerror(errno));
示例:
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <errno.h>
#include <string.h>
int main(int argc,char *argv[])
{
errno=0;
int iSqrt=sqrt((double)atoi(argv[1]));
if(errno)
perror("sqrt error");
else
printf("result is:%d/n",iSqrt);
return 0;
}
|
abort,exit
和atexit函数
abort函数终止整个进程,它是一个非常严厉的函数,会导致进程退出前一些正常的清理工作不能执行,
比如atexit函数不会被调用,对象不能自动调用析构函数等。
例:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
class CTest
{
public:
~CTest()
{
printf("~CTest()/n");
}
};
CTest g_obj;
int main(int argc,char *argv[])
{
abort();
return 0;
}
|
exit(int iStatus)也是进程退出函数,它相似于main函数的自然返回,能够进行正确的清理工作,并且能否是进程的退出码置为iStatus。
例:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
class CTest
{
public:
~CTest()
{
printf("~CTest()/n");
}
};
CTest g_obj;
int main(int argc,char *argv[])
{
exit(EXIT_SUCCESS);
}
|
注意:main函数没返回值,在g++编译中是认为正确的。
atexit函数是注册一个回调函数,在进程退出前,先自动调用该回调函数,所以如果你想在进程退出前做一些清理工作,你可将这些清理工作的代码写到该回调函数中。
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void MyClean()
{
printf("Yes,I'm cleaning/n");
}
int main(int argc,char *argv[])
{
atexit(MyClean);
exit(EXIT_SUCCESS);
}
|
在大多数Linux系统中,系统日志位于/var/log目录下。这些日志文件包括messages、debug、mail、news。
函数syslog可以往其中的日志文件中写入日志消息字符串。
原型为:
#include <syslog.h>
void syslog(int iPriority,char *pszFormat,...)
iPriority由一组掩码构成,常用的为LOG_INFO
pszFormat为格式化字符串,syslog接下来的参数必须与pszFormat字符串匹配
示例:
#include <stdlib.h>
#include <stdio.h>
#include <syslog.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc,char *argv[])s
{
syslog(LOG_INFO,"Ghaha test messsage/n");
return 0;
}
|
在/var/log/messages文件中将追加一条类似如下的信息:
Sep 23 14:43:20 LinuxServer a.out: Ghaha test messsage
如果想随时跟踪日志文件的变化,看a.out程序往系统日志文件中写了写什么信息,可以
在shell中执行
tail -f /var/log/messages |grep a.out
注意必须要超级用户的权限
练习1:
往名字空间tools中添加两个日志写入函数,
原型为:
bool Log(const char *pszFormat,...);
bool LogFile(const char *pszFile,const char *pszFormat,...);
函数Log的功能是往当前目录的log.txt文件中写入一行由pszFormat格式化的字符串,每一行日志的前面
必须加上当前的时间。
例如有如下调用
Log("Test messsage is:%d",1);
则应该在当前目录的log.txt文件末尾追加一行:
2005-09-23 15:30:02 Test messsage is:1
函数LogFile的功能同Log类似,只是日志文件名由pszFile指定
例如有如下调用
LogFile("logerr.txt","Test messsage is:%d",9);
则应该在当前目录的logerr.txt文件末尾追加一行:
2005-09-23 15:30:02 Test messsage is:9
bool LogMem(void *pData,int iSize);