c++ c/s体系结构
2007-11-15 09:35:00 guo_wangwei 阅读数 1306
Part I 如何上路
1. vi, vim是编译器么?
vi means visual editor,是软件世界第一个全屏幕编辑器,最初的作者是现在Sun microsystem的Bill Joy。
vim means Vi IMproved,可以看作是增强的vi。
很不幸,他们都不是编译器,如果你已经写好了first.c,那么不能指望vi们将你的源代码变成执行程序。

2. gcc, g++这些都是干什么用的
gcc means GNU C Collector,是GNU的旗舰软件,自由软件,C语言编译器。  
g++ 是GNU的C++编译器。

3. 那么cc, CC, ld, make这些程序又是干什么的呢?
cc是unix world中对c编译器的叫法,就是c compiler。
CC是对c++编译器的叫法,这两个名称都不特指某一厂家的产品。例如HP提供的HP-UX上c编译器叫cc,Solaris上的c编译器也叫cc
ld means link editor,是连接器的通称,并不特指某一个具体的产品。但是他们都是用来连接目标文件的。
make means ???,make程序根据Makefile/makefile中指定的规则,以及一些默认的规则,完成从源代码到最终
代码的处理过程。不光可以用来编译连接程序,也可以做其它的一些有依赖,分阶段的事情。

4. 我已经安装了linux,如何开始我的C/C++之旅呢?
step 1: typein a helloworld program(1) in your favorate editor, I think it must be a vim
step 2: save your program to hello.c and then quit from the editor
step 3: typein 'gcc hello.c'
step 4: typein 'a.out'

这时候,你应该可以看到你的第一个程序已经顺利运行了!
我们将上面的四个步骤概括一下,然后遵循这几个步骤,你就可以不断的生产出各样的程序了

第一步:编辑程序
第二步:编译程序
第三步:运行程序

5. 我的程序运行结果与我想象的不同!
如果你的第一反应是"printf这个函数(或者gcc或者其他的库什么的)有问题!",那么我想你是个自恋狂,记住,整个系统中唯一没有经过测试的就是你的代码,任何东西都是没有问题的,除了你的代码。(当然gcc也可能有问题,但是被你遇到的几率可以暂时看作是0)

我们有两种方式来解决这个问题:

第一种方式是使用printf函数在可能出问题的地方输出相关变量,好处是可以快速的上手,不需要其他的知识。坏处是如果你没有足够的技巧,你有可能忘记删除这些函数,以及在程序比较大的时候每次增加了新的printf都要重新编译,太浪费时间。

第二种方式是使用调试器,比如gdb。但是gdb就象你知道的其他大部分调试器一样,是符号调试器,他们依赖于编译器产生的符号表。符号表通常可以通过给编译器指定-g参数来生成。如果没有符号表,gdb很难使用(仍然可以使用,如果你熟悉汇编语言的话)。

6. Core dump!
你的程序现在已经很复杂了,在你增加了某一个十分强劲的功能后,一执行,屏幕上出现一行小字:
       Segmentation fault(core dump)
然后一切就都安静了下来。可以说太糟糕了,unix所能提供的最坏的界面都让你遇到了。怎么办呢?

如果你记得了在编译程序的时候使用-g参数,那么现在它就派上了用场。你可以:
       gdb a.out core
然后你就可以通过gdb的where命令查看问题出在了什么地方。

segmentation fault的意思是段违例,一般由于你的程序越界写造成。例如你的数组长度是8,但是你企图向相当于第10个元素的位置写入数据,就可能会产生这个问题。core dump产生的原因不止是segment fault,还有可能是其他的,总之是因为有坏人向你的程序发送了一个不可捕获的信号。如果这句话的意思你不明白,没有关系,也不需要明白,那是以后的事。

7. printf的头文件在哪里?
你在星巴克里跟女朋友聊天并同时向邻座的单身女孩抛媚眼的时候,脑子里还在想一个想了很长时间但是一直没有答案的问题,到底如何向屏幕输出一行文字呢?这时候,两个笨蛋从你旁边经过,他们正在讨论printf,你听到后,觉得:哦这才是我想要的,对printf,没错。但是你那如编译器一般的大脑马上提醒你,找不到函数原型,应该包含什么头文件呢
于是你停止聊天和抛接媚眼,打开手提电脑,通过某种无线装置接入到internet,在bbs上发了一个帖子:
       where the printf() is defined?
但是出乎你的意料,尽管这是一个刚果人都经常光顾的bbs,但是居然过去了5分钟之后,仍然没有人回答你。看来这个问题偏难,你微笑着对你的女朋友说。

事实上,你不应该问这样的问题。你应该学会自己解决这样的问题,我提供给你几个途径:

man printf(如果该关键字有多个entry,则应该用man -a或者man -k,或者直接指定section)
find /usr/include -name "*.h" -print | xargs grep printf
search on Google

8. 我已经有多个.c 文件了,应该如何编译呢?
经过一段时间的开发,你的程序目前已经从一个简单的foo.c变成了两个文件,foo.c和bar.c,我们假定foo.c中定义了main函数。那么:
       gcc -g bar.c
将报告你没有main函数。这让你很恼火,是否应该合并两个文件呢?还是。。。?

正确的做法是编译时刻增加一个“只编译”的参数-c:
       gcc -g -c bar.c
       gcc -g -c foo.c
       gcc -o a.out foo.o bar.o

这样之后,你的程序可以运行了。我们在前面提到三个步骤,编辑,编译,运行。但实际上,我们忽略了一个重要的步骤就是连接,我们前面的例子中,编译和连接都是一步完成的(不指定-c 的话),因此我们没有提起。但是你大概会问,连接不应该是用ld么?为什么在这里用gcc完成了呢?

ld当然是可以完成任务的,但是它并不知道我们在写一个c程序,c程序的main函数是由_start()函数调用的,而start函数是在runtime目标文件中(通常叫做xxcrt.o)实现的,任何c程序都必须连接这个runtime目标文件。如果用ld作为连接器,我们不得不自己指定这个目标文件的位置以及文件名。但是,如果用gcc,则方便的多,它知道我们要额外连接那些东西,它提供给我们一个更简单的使用界面,尽管它仍然是通过调用ld来工作的。

9. Why a.out?
迄今为止,你发现你的执行程序一直叫a.out,这个名字很古怪,也很土,你说呢?如果你想改变一下你的程序名字,应该:
       gcc -o win.exe foo.c bar.c
这样,你的程序就叫做win.exe,而不是a.out了。 a.out这个名字的起源估计是某人的一时冲动,例如我小时侯经常把程序中的变量依次称为aint, bint...(绝对的坏习惯,这里先不说这个)。但是后来执行文件因此得名,甚至执行文件的格式也因此得名(早期的unix执行文件格式称为a.out格式,当然现在已经进化为ELF了)。

10. 如何生成动态库和静态库?
静态库是一个目标文件的简单集合。由ar(archive,归档的意思)生成。
       ar -cr libfoo.a foo.o bar.o
通常命名方式是libxxx.a,但是你不遵守也没有太大的问题。应用程序在使用你的库的时候,通常只需要告诉ld你的库名字即可,这个名字就是libxxx.a中的xxx,例如ld -lfoo。意思是告诉ld,连接一个名字为libfoo.a或者libfoo.so的库。如果你的库名字不遵循libxxx.a的格式,ld就找不到,给应用开发造成麻烦。

另外,静态的意思是每个用到该库的应用程序都拥有一份自己的库拷贝,应用程序运行的时候,即使将库删除也没有问题
因为应用程序自己已经有了自己的拷贝。

动态库结构复杂一些,通常是一个ELF格式的文件。可以有三种方法生成:

ld -G
gcc -share
libtool
用ld最复杂,用gcc -share就简单的多,但是-share并非在任何平台都可以使用。GNU提供了一个更好的工具libtool,专门用来在各种平台上生成各种库。

动态库实际上应该叫做共享库,只是很多人从windows的Dynamic Linked Library这个词学习过来,把unix的共享库称做动态库。所有应用程序共享一份库拷贝,所以,即使连接完了,也不能将其删除。而且需要在LD_LIBRARY_PATH这个环境变量中正确的设置库所在的位置,否则程序运行会报告找不到这个库。

11. 我有了10个.c文件,还是一个一个编译么?有没有工程的概念(就象vc的dsp)?
确实一个一个编译很土。我们有更好的办法,就是make。make程序是一个类似脚本执行程序一样的东西。它根据你提供的Makefile(或者小写的makefile)来工作,它可以处理复杂的依赖关系,就象你希望的那样,如果修改了一个头文件,那么包含它的所有.c程序都应该被重新编译。但是很不幸,这种依赖关系需要你自己指定。你首先要了解makefile的语法,然后根据语法来写makefile。当程序很多得时候,makefile也变得复杂。如果你希望得到makefile得更详细信息,可以
       man make
或者在linux里面:
       info make
但是没有更简单的办法么?好在世界上除了你我之外还有很多人注意到了这个问题。目前有两个简单的办法:

imake,imake是依赖已经建立好得一个库信息数据库,可以帮助你完成连接遇到的问题,尤其是写X Window程序,很多人用imake
automake/autoconf,这两个程序更加完善和简单。但是使用稍微复杂一些,你需要看更多的手册才能掌握,但是非常好用,简单到如果你增加了一个.c文件,只需要在Makefile.am中增加一个文件名即可,头文件的依赖完全自动生成。
这两个简单的办法已经超过了新手可以接受的范围,如果你确实是新手,还是学着自己写makefile好一些。

12. 我要学习linux kernel的源代码,遇到了一些问题,能告诉我怎么办么?
如果前面的问题有你不清楚的,我建议你还是找一本浅显一些的教材,然后敲些例子程序来学习。现在大家的趋势好象是言必称kernel,好象不太对劲。学习的对象如果不适合自己的层次,只能导致进度减慢。

13. printf()这个函数如何使用?
这个问题好象与前面的问题类似,但是因为太多人问类似的问题,所以只好单独列出来了。你不应该问这样的问题,你应该首先想到的是man,这个伟大的助手。

       man printf

当然,不同的OS,能够提供给你的man略有不同,例如man的参数等等。所以当你接触到一个新的unix变种的时候,有必要先:

       man man

这样可以知道man应该如何使用。

另外,当man解决不了你的问题的时候,最好的办法是自己写一个测试程序,在自己的$HOME中保持一个test目录是我的习惯,遇到任何不能肯定的问题,都可以在这里先实验一番。这些办法都比到bbs上去提问高效。

Part II 语言
1. Windows vs Linux?
这里扯出这个问题好象有些奇怪。这个文档主要是以linux为背景讲的,因此很少涉及到Windows平台下面的东西。但是这不等于说Windows不好,只是顾及了我自己的一些偏好。开始学习的初期,这些因素的影响不大,不用加入到孰优孰劣的无聊争论中。

2. 我要学习C++,需要C语言的知识么?
C++和C这两种语言的关系在The C++ Programming Language这本书的1.6节讲的已经很清楚了,如果你有什么疑问,可以仔细读一读。应该可以不需要C语言的知识就可以开始学习C++,但是有些C的基本常识,再学习C++,肯定是有帮助的。

3. 哪些东西应该放进头文件中,哪些不应该?
头文件相当于一个模块接口的描述,应该尽可能的简单明了。

我们可以根据下面的公式来判断哪些东西应该进入头文件,哪些只要在源程序中声明就可以了:

这个结构是否会在其它源程序中被使用?
yes
进入头文件
no
不进入头文件
实际上,还会有一些被“株连”的结构啊,宏啊什么的被编译器要求放进头文件。例如:

struct A
{
    struct B b;  // 用到了另外一个结构
    int c;
};

这就是我所谓的“株连”。但是这里有点儿小技巧,比如struct B事关国家安全,绝对不能让别人知道它的结构,因此不能将其放进头文件中。这时候可以采用下面的方法:

struct B;          // 告诉编译器在某处声明了一个struct B
struct A
{
    struct B * b;  // 变成指针
    int c;
};

通过这种方法,你就可以将struct B的声明移到某个.c文件中,从而达到了隐藏信息的目的。

4. 宏是什么,预处理是什么意思?
你经常遇到一些#define之类的东西,这些东西是干什么用的?有什么作用?有人告诉你说这些叫做宏(macro),你对这个单词的中文和英文都很不能接受,甚至如果你看一些台湾的资料,把这些东西叫做“巨集”,这就更让你摸不到头脑了。这不奇怪,因为这实在是C语言当初从那些低级语言演变过来的过程中遗留的产物,现在时髦的语言比如VB,Java中都不存在了。

你可以这么理解,如果你不想在程序中重复的书写同一段代码,例如,你的程序中有一个结构,还有很多地方都需要对这个结构赋值,每次你都要写十几行同样的代码给它的每个成员一个初始值,很快你就感到厌烦了。你很想简单一些,可是不知道怎么办,这时候宏可以帮助你。你可以 声明一个宏,在这个宏中,对结构的每个成员赋值。然后在每次真的想赋值的时候,写一行代码就完成了。别急,别急,我知道你想说什么,你想说其实你有自己的办法,写一个函数不就可以了么?一样每次赋值只需要一行代码。你说的没错,这两种办法都可以,但是有一些区别。这种区别只有在编译后的汇编代码中你才能发现。为了避免你让我举出汇编代码的例子,我决定利用一下编译器。假设你有下面的程序:

#define setValue(x) x.a = 10; x.b=5; x.c=1;
struct S
{
    int a;
    int b;
    int c;
};
int main()
{
    struct S s;
    setValue(s);
    return 0;
}

该程序名为test.c。下面,我们执行:
gcc -E test.c -o test.txt
看看我们得到了什么?一个名字为test.txt的文件,这个文件的内容如下:

struct S
{
    int a;
    int b;
    int c;
};
int main()
{
    struct S s;
    s.a = 10; s.b=5; s.c=1;;
    return 0;
}
首先要解释一下,gcc -E的含义,-E这个参数表示要求gcc在进行预处理之后就停止,不要继续工作下去了,先休息休息。

5. 内存的分配和释放(静态,动态)
这是一个持久的话题,没有新手不在这上面绊蒜(栽跟头)的。很多老手常常在这个问题上告诫新手,这方面的问题以前也经常出现在面试的题目中 ,搞得十分神秘。

其实,你只要记得一句话,释放自己分配的内存。一切问题都可以应付,相信我,就这么简单,当然,如果你没记住,也没什么大不了的如何理解呢?执行一次动态内存分配,就应该记得执行一次内存的释放。例如一个malloc对应一个free,一个new对应一个delete。就这么简单。可为什么说记不住也没什么大不了呢?我说的是,在某些情况下,你忘记了这个一一对应的关系对程序也没什么影响,如果你不是在写一个daemon的话。进程终止后,自然一切进程空间内的东西都化为乌有,你忘记了释放又有什么关系呢?(尽管这样,我仍然建议你严格遵守前面的原则,我说没什么大不了的,只是为了澄清一点概念)

但是实际情况要复杂的多,例如:

任何时候,如果你需要一个内存块来做点儿什么的话,那么只有两个来源,堆和栈。它们有什么区别呢?其实非常简单,栈上的内存空间是在编译时刻由编译器划出来的,编译之后就已经确定了相对的地址,只要一运行,即使你什么都不作,它也立刻就存在了。堆上的内存空间则需要你的程序“动态”的,也就是在运行时刻,通知操作系统,由操作系统来完成分配,而在这之前,是不存在的。因此我们可以这么认为,栈上的内存不需要你释放,堆上的内存必须由你释放。

你使用了asctime这个函数,你发现它给你返回了一个字符串,这个字符串使用的空间是从哪里来的?是否要你释放一下呢?答案是不需要,C库中尽量避免了这种情况,就是返回给你一个动态分配的内存块,然后需要你自己来释放。它通常的策略是返回给你一个静态变量。如果你对这种方式不满(例如,你在写一个多线程的程序),你可以使用以_r结尾的相应函数代替。_r结尾的函数给了你更多的自主,你需要自己先搞到一块内存(堆上的或者栈上的),然后将这个内存块地址告诉它。因此,_r结尾的函数都是线程安全的,也全部比对应的函数多一个参数。通过使用_r结尾的函数,你就可以坚定的贯彻“释放自己分配的内存”这个原则了。

类似char * s = "this is a string";这样的语句,s这个指针指向的内存是否需要释放呢?答案还是不需要。"this is a string"所需要的内存在程序被编译的时候就已经确定下来了,在栈上分配。我们怎么判断这一点呢?好在linux给我们提供了足够的工具,我们可以用size这个程序来观察程序的代码(text)段。通过将这个字符串变长或者变短,我们会发现text段的长度也随之变化。而动态分配的内存大小对代码段的大小是没有影响的。这个内存块不是你分配的,所以你不要释放。

你已经使用free释放了一块内存,但是随后你发现如果你引用这块内存,还是可以得到与原来一样的内容。这也不奇怪,free只处理一个内存块的索引表,并不处理内存块中的内容。它在内存块的索引表中标识出这块内存处在free的状态,然后就返回。很多超级新手认为free会将内存块清0(或者其它的什么值),这是幻想。
同样的道理,如果你在函数中返回栈上的一个内存块的指针,在函数返回后仍然可以得到与在函数中一样的内容。这也是因为函数返回的时候,只是更改了栈指针,并没有人去管栈内的内容。当然前提是该函数返回后没有进行其它的函数调用,这样栈内内容就可以保持不变。但是一旦发生了函数调用,栈指针又向下压,栈内的内容就可能改变了。因此,绝对不能返回栈上的内存块指针,即使有的时候看上去很正确。

6.  linux编译的程序能不能在sun上跑

注1)  

如果你没有这样的例子,可以参照:
CODE:
#include <stdio.h>;

int main()
{
    printf("hello world/n");
    return 0;
}
 
2018-04-11 19:53:08 hz951290428 阅读数 893

Server.c

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <strings.h>
#include <string.h>
#include <ctype.h>
#include <arpa/inet.h>

#define SERV_PORT 9988

int main(void)
{
    int sfd, cfd;
    int len, i;
    char buf[BUFSIZ], clie_IP[BUFSIZ];

    struct sockaddr_in serv_addr, clie_addr;
    socklen_t clie_addr_len;

    /*创建一个socket 指定IPv4协议族 TCP协议*/
    sfd = socket(AF_INET, SOCK_STREAM, 0);

    /*初始化一个地址结构 man 7 ip 查看对应信息*/
    bzero(&serv_addr, sizeof(serv_addr));           //将整个结构体清零
    serv_addr.sin_family = AF_INET;                 //选择协议族为IPv4
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);  //监听本地所有IP地址
    serv_addr.sin_port = htons(SERV_PORT);          //绑定端口号    

    /*绑定服务器地址结构*/
    bind(sfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

    /*设定链接上限,注意此处不阻塞*/
    listen(sfd, 64);                                //同一时刻允许向服务器发起链接请求的数量

    printf("wait for client connect ...\n");

    /*获取客户端地址结构大小*/ 
    clie_addr_len = sizeof(clie_addr_len);
    /*参数1是sfd; 参2传出参数, 参3传入传入参数, 全部是client端的参数*/
    cfd = accept(sfd, (struct sockaddr *)&clie_addr, &clie_addr_len);     /*监听客户端链接, 会阻塞*/

    printf("client IP:%s\tport:%d\n", 
            inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clie_IP, sizeof(clie_IP)), 
            ntohs(clie_addr.sin_port));

    while (1) {
        /*读取客户端发送数据*/
        len = read(cfd, buf, sizeof(buf));
        write(STDOUT_FILENO, buf, len);

        /*处理客户端数据*/
        for (i = 0; i < len; i++)
            buf[i] = toupper(buf[i]);

        /*处理完数据回写给客户端*/
        write(cfd, buf, len); 
    }

    /*关闭链接*/
    close(sfd);
    close(cfd);

    return 0;
}

Client.c

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define SERV_IP "127.0.0.1"
#define SERV_PORT 9988

int main(void)
{
    int sfd, len;
    struct sockaddr_in serv_addr;
    char buf[BUFSIZ]; 

    /*创建一个socket 指定IPv4 TCP*/
    sfd = socket(AF_INET, SOCK_STREAM, 0);

    /*初始化一个地址结构:*/
    bzero(&serv_addr, sizeof(serv_addr));                       //清零
    serv_addr.sin_family = AF_INET;                             //IPv4协议族
    inet_pton(AF_INET, SERV_IP, &serv_addr.sin_addr.s_addr);    //指定IP 字符串类型转换为网络字节序 参3:传出参数
    serv_addr.sin_port = htons(SERV_PORT);                      //指定端口 本地转网络字节序

    /*根据地址结构链接指定服务器进程*/
    connect(sfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

    while (1) {
        /*从标准输入获取数据*/
        fgets(buf, sizeof(buf), stdin);
        /*将数据写给服务器*/
        write(sfd, buf, strlen(buf));       //写个服务器
        /*从服务器读回转换后数据*/
        len = read(sfd, buf, sizeof(buf));
        /*写至标准输出*/
        write(STDOUT_FILENO, buf, len);
    }

    /*关闭链接*/
    close(sfd);

    return 0;
}
2018-10-25 17:45:06 u011233383 阅读数 550

实现概述

现在很多的编程语言提供拆开即用的Client/Server的框架代码,比如Java的Netty,用起来超级舒服、简单。python编程语言的更简单,忘记用的什么框架了。最近在C++上工作,想来不如用C++试验一下。

代码实现的功能:
首先服务器等待客户端连接,连接完毕之后,向客户端发送一个整形值,客户端收到整形值,打印,然后发送“hello”给服务器,服务器收到之后打印出来,然后发送之前整形值加一,以此循环。

下面的代码没有区分,没有直接说明的话,均使用下述的头文件:

#include <boost/asio.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <string>
#include <vector>
#include <iostream>
#include <memory>
#include <boost/bind.hpp>
#include <boost/cstdint.hpp>
#include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/condition.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/ip/tcp.hpp>

using namespace boost;
using namespace boost::asio;
using namespace std;

typedef ip::tcp::acceptor acceptor;
typedef ip::tcp::endpoint endpoint;
typedef std::shared_ptr<ip::tcp::socket> socket_ptr;

Client 代码

废话不多说,直接上代码。

// client.cpp
class client
{
public:
    client() : m_ep(ip::address::from_string("127.0.0.1"), 8002)
    {
        start();
    }
    void run()
    {
        m_io.run();
    }
private:
    void start()
    {
        socket_ptr sock(new ip::tcp::socket(m_io));
        sock->async_connect(m_ep, bind(&client::conn_handler, this, boost::asio::placeholders::error, sock));
    }
    
    void conn_handler(const boost::system::error_code& ec, socket_ptr sock)
    {
        if (ec) return;
        cout << "receive from" << sock->remote_endpoint().address() << endl;
        sock->async_read_some(buffer(m_buffer, 100), bind(&client::read_handler, this, boost::asio::placeholders::error, sock));
    }
    void read_handler(const boost::system::error_code& ec, socket_ptr sock)
    {
        std::string a = "hello";
        auto b = std::make_shared<std::string>(a);
        cout << "get:" << m_buffer << endl;
        sock->async_write_some(buffer(*b), bind(&client::conn_handler, this, boost::asio::placeholders::error, sock));
    }
    io_service m_io;
    char m_buffer[100];
    endpoint m_ep;
};

int main()
{
	client s1;
	s1.run();
	cout << "yes" << endl;
	return 0;
}

Server代码

还是直接上代码:

// server.cpp
class server
{
public:
    server() : m_acceptor(m_io, endpoint(ip::tcp::v4(), 6688))
    {
        accept();
    }
    void run()
    {
        m_io.run();
    }
private:
    void accept()
    {
        socket_ptr sock(new ip::tcp::socket(m_io));
        m_acceptor.async_accept(*sock, bind(&server::accept_handler, this, boost::asio::placeholders::error, sock));
    }
    
    void accept_handler(const boost::system::error_code& ec, socket_ptr sock)
    {
        if (ec)return;
        cout << "client:" << endl;
        cout << sock->remote_endpoint().address() << endl;
        sock->async_write_some(buffer(boost::lexical_cast<std::string>(++a)), bind(&server::write_handler, this, boost::asio::placeholders::error, sock));
        accept();
    }
    void write_handler(const boost::system::error_code& ec, socket_ptr sock)
    {
        if (ec)return;
        sock->async_read_some(buffer(m_buffer, 100), bind(&server::read_handler, this, boost::asio::placeholders::error, sock));
    }
    void read_handler(const boost::system::error_code& ec, socket_ptr sock)
    {
        if (ec)return;
        cout << "read something:" << m_buffer << endl;
        cout << "ready to write again" << endl;
        sock->async_write_some(buffer(boost::lexical_cast<std::string>(++a)), bind(&server::write_handler, this, boost::asio::placeholders::error, sock));
    }
    io_service m_io;
    acceptor m_acceptor;
    int a = 0;
    char m_buffer[100];
};

int main()
{
    server s1;
	s1.run();
	cout << "yes" << endl;
	return 0;
    return 0;
}

编译命令

编译之前需要先安装g++, 代码在Linux平台编译,然后需要安装上boost依赖库,需要找到对应的boost源代码安装目录,以及相应的库安装目录,本人的机子上分别安装在下边两个目录

boost源码: /usr/include/boost159/
boost链接库:/usr/lib64/boost159

编译client.cpp

g++ client.cpp -std=c++11 -o client -I /usr/include/boost159/ -L /usr/lib64/boost159/ -lboost_system

结果是编译出执行文件client,这个是客户端运行文件。

编译server.cpp

g++ server.cpp -std=c++11 -o server -I /usr/include/boost159/ -L /usr/lib64/boost159/ -lboost_system

结果是编译出执行文件client,这个是客户端运行文件。

运行代码

  1. 首先运行server

./server //server will wait the client will connect in.

  1. 然后运行client

./client //client will connect server right now.

running

2009-11-17 10:04:00 lijun624 阅读数 248

 

Part I 如何上路

1. vi, vim是编译器么?

vi means visual editor,是软件世界第一个全屏幕编辑器,最初的作者是现在Sun microsystem的Bill Joy。

vim means Vi IMproved,可以看作是增强的vi。

很不幸,他们都不是编译器,如果你已经写好了first.c,那么不能指望vi们将你的源代码变成执行程序。

2. gcc, g++这些都是干什么用的

gcc means GNU C Collector,是GNU的旗舰软件,自由软件,C语言编译器。  

g++ 是GNU的C++编译器。

3. 那么cc, CC, ld, make这些程序又是干什么的呢?

cc是unix world中对c编译器的叫法,就是c compiler。

CC是对c++编译器的叫法,这两个名称都不特指某一厂家的产品。例如HP提供的HP-UX上c编译器叫cc,Solaris上

的c编译器也叫cc

ld means link editor,是连接器的通称,并不特指某一个具体的产品。但是他们都是用来连接目标文件的。

make means ???,make程序根据Makefile/makefile中指定的规则,以及一些默认的规则,完成从源代码到最终

代码的处理过程。不光可以用来编译连接程序,也可以做其它的一些有依赖,分阶段的事情。

4. 我已经安装了linux,如何开始我的C/C++之旅呢?

step 1: typein a helloworld program(1) in your favorate editor, I think it must be a vim

step 2: save your program to hello.c and then quit from the editor

step 3: typein 'gcc hello.c'

step 4: typein 'a.out'

这时候,你应该可以看到你的第一个程序已经顺利运行了!

我们将上面的四个步骤概括一下,然后遵循这几个步骤,你就可以不断的生产出各样的程序了

第一步:编辑程序

第二步:编译程序

第三步:运行程序

5. 我的程序运行结果与我想象的不同!

如果你的第一反应是"printf这个函数(或者gcc或者其他的库什么的)有问题!",那么我想你是个自恋狂,记住,整个

系统中唯一没有经过测试的就是你的代码,任何东西都是没有问题的,除了你的代码。(当然gcc也可能有问题,但是被

你遇到的几率可以暂时看作是0)

我们有两种方式来解决这个问题:

第一种方式是使用printf函数在可能出问题的地方输出相关变量,好处是可以快速的上手,不需要其他的知识。坏处是

如果你没有足够的技巧,你有可能忘记删除这些函数,以及在程序比较大的时候每次增加了新的printf都要重新编译,

太浪费时间。

第二种方式是使用调试器,比如gdb。但是gdb就象你知道的其他大部分调试器一样,是符号调试器,他们依赖于编译器

产生的符号表。符号表通常可以通过给编译器指定-g参数来生成。如果没有符号表,gdb很难使用(仍然可以使用,如果

你熟悉汇编语言的话)。

6. Core dump!

你的程序现在已经很复杂了,在你增加了某一个十分强劲的功能后,一执行,屏幕上出现一行小字:

       Segmentation fault(core dump)

然后一切就都安静了下来。可以说太糟糕了,unix所能提供的最坏的界面都让你遇到了。怎么办呢?

如果你记得了在编译程序的时候使用-g参数,那么现在它就派上了用场。你可以:

       gdb a.out core

然后你就可以通过gdb的where命令查看问题出在了什么地方。

segmentation fault的意思是段违例,一般由于你的程序越界写造成。例如你的数组长度是8,但是你企图向相当于第10个元

素的位置写入数据,就可能会产生这个问题。core dump产生的原因不止是segment fault,还有可能是其他的,总之是因为有坏人向你的程序发送了一个不可捕获的信号。如果这句话的意思你不明白,没有关系,也不需要明白,那是以后的事。

7. printf的头文件在哪里?

你在星巴克里跟女朋友聊天并同时向邻座的单身女孩抛媚眼的时候,脑子里还在想一个想了很长时间但是一直没有答案的

问题,到底如何向屏幕输出一行文字呢?这时候,两个笨蛋从你旁边经过,他们正在讨论printf,你听到后,觉得:哦

这才是我想要的,对printf,没错。但是你那如编译器一般的大脑马上提醒你,找不到函数原型,应该包含什么头文件呢

于是你停止聊天和抛接媚眼,打开手提电脑,通过某种无线装置接入到internet,在bbs上发了一个帖子:

       where the printf() is defined?

但是出乎你的意料,尽管这是一个刚果人都经常光顾的bbs,但是居然过去了5分钟之后,仍然没有人回答你。看来这个问

题偏难,你微笑着对你的女朋友说。

事实上,你不应该问这样的问题。你应该学会自己解决这样的问题,我提供给你几个途径:

man printf(如果该关键字有多个entry,则应该用man -a或者man -k,或者直接指定section)

find /usr/include -name "*.h" -print | xargs grep printf

search on Google

8. 我已经有多个.c 文件了,应该如何编译呢?

经过一段时间的开发,你的程序目前已经从一个简单的foo.c变成了两个文件,foo.c和bar.c,我们假定foo.c中定义了

main函数。那么:

       gcc -g bar.c

将报告你没有main函数。这让你很恼火,是否应该合并两个文件呢?还是。。。?

正确的做法是编译时刻增加一个“只编译”的参数-c:

       gcc -g -c bar.c

       gcc -g -c foo.c

       gcc -o a.out foo.o bar.o

这样之后,你的程序可以运行了。我们在前面提到三个步骤,编辑,编译,运行。但实际上,我们忽略了一个重要的步骤

就是连接,我们前面的例子中,编译和连接都是一步完成的(不指定-c 的话),因此我们没有提起。但是你大概会问,

连接不应该是用ld么?为什么在这里用gcc完成了呢?

ld当然是可以完成任务的,但是它并不知道我们在写一个c程序,c程序的main函数是由_start()函数调用的,而start

函数是在runtime目标文件中(通常叫做xxcrt.o)实现的,任何c程序都必须连接这个runtime目标文件。如果用ld作为

连接器,我们不得不自己指定这个目标文件的位置以及文件名。但是,如果用gcc,则方便的多,它知道我们要额外连接

那些东西,它提供给我们一个更简单的使用界面,尽管它仍然是通过调用ld来工作的。

9. Why a.out?

迄今为止,你发现你的执行程序一直叫a.out,这个名字很古怪,也很土,你说呢?如果你想改变一下你的程序名字,应

该:

       gcc -o win.exe foo.c bar.c

这样,你的程序就叫做win.exe,而不是a.out了。 a.out这个名字的起源估计是某人的一时冲动,例如我小时侯经常把

程序中的变量依次称为aint, bint...(绝对的坏习惯,这里先不说这个)。但是后来执行文件因此得名,甚至执行文件的

格式也因此得名(早期的unix执行文件格式称为a.out格式,当然现在已经进化为ELF了)。

10. 如何生成动态库和静态库?

静态库是一个目标文件的简单集合。由ar(archive,归档的意思)生成。

       ar -cr libfoo.a foo.o bar.o

通常命名方式是libxxx.a,但是你不遵守也没有太大的问题。应用程序在使用你的库的时候,通常只需要告诉ld你的库

名字即可,这个名字就是libxxx.a中的xxx,例如ld -lfoo。意思是告诉ld,连接一个名字为libfoo.a或者libfoo.so

的库。如果你的库名字不遵循libxxx.a的格式,ld就找不到,给应用开发造成麻烦。

另外,静态的意思是每个用到该库的应用程序都拥有一份自己的库拷贝,应用程序运行的时候,即使将库删除也没有问题

因为应用程序自己已经有了自己的拷贝。

动态库结构复杂一些,通常是一个ELF格式的文件。可以有三种方法生成:

ld -G

gcc -share

libtool

用ld最复杂,用gcc -share就简单的多,但是-share并非在任何平台都可以使用。GNU提供了一个更好的工具libtool,

专门用来在各种平台上生成各种库。

动态库实际上应该叫做共享库,只是很多人从windows的Dynamic Linked Library这个词学习过来,把unix的共享库称

做动态库。所有应用程序共享一份库拷贝,所以,即使连接完了,也不能将其删除。而且需要在LD_LIBRARY_PATH这个环

境变量中正确的设置库所在的位置,否则程序运行会报告找不到这个库。

11. 我有了10个.c文件,还是一个一个编译么?有没有工程的概念(就象vc的dsp)?

确实一个一个编译很土。我们有更好的办法,就是make。make程序是一个类似脚本执行程序一样的东西。它根据你提供的

Makefile(或者小写的makefile)来工作,它可以处理复杂的依赖关系,就象你希望的那样,如果修改了一个头文件,那

么包含它的所有.c程序都应该被重新编译。但是很不幸,这种依赖关系需要你自己指定。你首先要了解makefile的语法,

然后根据语法来写makefile。当程序很多得时候,makefile也变得复杂。如果你希望得到makefile得更详细信息,可以

       man make

或者在linux里面:

       info make

但是没有更简单的办法么?好在世界上除了你我之外还有很多人注意到了这个问题。目前有两个简单的办法:

imake,imake是依赖已经建立好得一个库信息数据库,可以帮助你完成连接遇到的问题,尤其是写X Window程序,很

多人用imake

automake/autoconf,这两个程序更加完善和简单。但是使用稍微复杂一些,你需要看更多的手册才能掌握,但是非常

好用,简单到如果你增加了一个.c文件,只需要在Makefile.am中增加一个文件名即可,头文件的依赖完全自动生成。

这两个简单的办法已经超过了新手可以接受的范围,如果你确实是新手,还是学着自己写makefile好一些。

12. 我要学习linux kernel的源代码,遇到了一些问题,能告诉我怎么办么?

如果前面的问题有你不清楚的,我建议你还是找一本浅显一些的教材,然后敲些例子程序来学习。现在大家的趋势好象是言必称kernel,好象不太对劲。学习的对象如果不适合自己的层次,只能导致进度减慢。

13. printf()这个函数如何使用?

这个问题好象与前面的问题类似,但是因为太多人问类似的问题,所以只好单独列出来了。你不应该问这样的问题,你应该首先想到的是man,这个伟大的助手。

       man printf

当然,不同的OS,能够提供给你的man略有不同,例如man的参数等等。所以当你接触到一个新的unix变种的时候,有必要先:

       man man

这样可以知道man应该如何使用。

另外,当man解决不了你的问题的时候,最好的办法是自己写一个测试程序,在自己的$HOME中保持一个test目录是我的习惯,遇到任何不能肯定的问题,都可以在这里先实验一番。这些办法都比到bbs上去提问高效。

Part II 语言

1. Windows vs Linux?

这里扯出这个问题好象有些奇怪。这个文档主要是以linux为背景讲的,因此很少涉及到Windows平台下面的东西。但是这不等于说Windows不好,只是顾及了我自己的一些偏好。开始学习的初期,这些因素的影响不大,不用加入到孰优孰劣的无聊争论中。

2. 我要学习C++,需要C语言的知识么?

C++和C这两种语言的关系在The C++ Programming Language这本书的1.6节讲的已经很清楚了,如果你有什么疑问,可以仔细读一读。应该可以不需要C语言的知识就可以开始学习C++,但是有些C的基本常识,再学习C++,肯定是有帮助的。

3. 哪些东西应该放进头文件中,哪些不应该?

头文件相当于一个模块接口的描述,应该尽可能的简单明了。

我们可以根据下面的公式来判断哪些东西应该进入头文件,哪些只要在源程序中声明就可以了:

这个结构是否会在其它源程序中被使用?

yes

进入头文件

no

不进入头文件

实际上,还会有一些被“株连”的结构啊,宏啊什么的被编译器要求放进头文件。例如:

struct A

{

    struct B b;  // 用到了另外一个结构

    int c;

};

这就是我所谓的“株连”。但是这里有点儿小技巧,比如struct B事关国家安全,绝对不能让别人知道它的结构,因此不能将其放进头文件中。这时候可以采用下面的方法:

struct B;          // 告诉编译器在某处声明了一个struct B

struct A

{

    struct B * b;  // 变成指针

    int c;

};

通过这种方法,你就可以将struct B的声明移到某个.c文件中,从而达到了隐藏信息的目的。

4. 宏是什么,预处理是什么意思?

你经常遇到一些#define之类的东西,这些东西是干什么用的?有什么作用?有人告诉你说这些叫做宏(macro),你对这个单词的中文和英文都很不能接受,甚至如果你看一些台湾的资料,把这些东西叫做“巨集”,这就更让你摸不到头脑了。这不奇怪,因为这实在是C语言当初从那些低级语言演变过来的过程中遗留的产物,现在时髦的语言比如VB,Java中都不存在了。

你可以这么理解,如果你不想在程序中重复的书写同一段代码,例如,你的程序中有一个结构,还有很多地方都需要对这个结构赋值,每次你都要写十几行同样的代码给它的每个成员一个初始值,很快你就感到厌烦了。你很想简单一些,可是不知道怎么办,这时候宏可以帮助你。你可以 声明一个宏,在这个宏中,对结构的每个成员赋值。然后在每次真的想赋值的时候,写一行代码就完成了。别急,别急,我知道你想说什么,你想说其实你有自己的办法,写一个函数不就可以了么?一样每次赋值只需要一行代码。你说的没错,这两种办法都可以,但是有一些区别。这种区别只有在编译后的汇编代码中你才能发现。为了避免你让我举出汇编代码的例子,我决定利用一下编译器。假设你有下面的程序:

#define setValue(x) x.a = 10; x.b=5; x.c=1;

struct S

{

    int a;

    int b;

    int c;

};

int main()

{

    struct S s;

    setValue(s);

    return 0;

}

该程序名为test.c。下面,我们执行:

gcc -E test.c -o test.txt

看看我们得到了什么?一个名字为test.txt的文件,这个文件的内容如下:

struct S

{

    int a;

    int b;

    int c;

};

int main()

{

    struct S s;

    s.a = 10; s.b=5; s.c=1;;

    return 0;

}

首先要解释一下,gcc -E的含义,-E这个参数表示要求gcc在进行预处理之后就停止,不要继续工作下去了,先休息休息。

5. 内存的分配和释放(静态,动态)

这是一个持久的话题,没有新手不在这上面绊蒜(栽跟头)的。很多老手常常在这个问题上告诫新手,这方面的问题以前也经常出现在面试的题目中 ,搞得十分神秘。

其实,你只要记得一句话,释放自己分配的内存。一切问题都可以应付,相信我,就这么简单,当然,如果你没记住,也没什么大不了的如何理解呢?执行一次动态内存分配,就应该记得执行一次内存的释放。例如一个malloc对应一个free,一个new对应一个delete。就这么简单。可为什么说记不住也没什么大不了呢?我说的是,在某些情况下,你忘记了这个一一对应的关系对程序也没什么影响,如果你不是在写一个daemon的话。进程终止后,自然一切进程空间内的东西都化为乌有,你忘记了释放又有什么关系呢?(尽管这样,我仍然建议你严格遵守前面的原则,我说没什么大不了的,只是为了澄清一点概念)

但是实际情况要复杂的多,例如:

任何时候,如果你需要一个内存块来做点儿什么的话,那么只有两个来源,堆和栈。它们有什么区别呢?其实非常简单,栈上的内存空间是在编译时刻由编译器划出来的,编译之后就已经确定了相对的地址,只要一运行,即使你什么都不作,它也立刻就存在了。堆上的内存空间则需要你的程序“动态”的,也就是在运行时刻,通知操作系统,由操作系统来完成分配,而在这之前,是不存在的。因此我们可以这么认为,栈上的内存不需要你释放,堆上的内存必须由你释放。

你使用了asctime这个函数,你发现它给你返回了一个字符串,这个字符串使用的空间是从哪里来的?是否要你释放一下呢?答案是不需要,C库中尽量避免了这种情况,就是返回给你一个动态分配的内存块,然后需要你自己来释放。它通常的策略是返回给你一个静态变量。如果你对这种方式不满(例如,你在写一个多线程的程序),你可以使用以_r结尾的相应函数代替。_r结尾的函数给了你更多的自主,你需要自己先搞到一块内存(堆上的或者栈上的),然后将这个内存块地址告诉它。因此,_r结尾的函数都是线程安全的,也全部比对应的函数多一个参数。通过使用_r结尾的函数,你就可以坚定的贯彻“释放自己分配的内存”这个原则了。

类似char * s = "this is a string";这样的语句,s这个指针指向的内存是否需要释放呢?答案还是不需要。"this is a string"所需要的内存在程序被编译的时候就已经确定下来了,在栈上分配。我们怎么判断这一点呢?好在linux给我们提供了足够的工具,我们可以用size这个程序来观察程序的代码(text)段。通过将这个字符串变长或者变短,我们会发现text段的长度也随之变化。而动态分配的内存大小对代码段的大小是没有影响的。这个内存块不是你分配的,所以你不要释放。

你已经使用free释放了一块内存,但是随后你发现如果你引用这块内存,还是可以得到与原来一样的内容。这也不奇怪,free只处理一个内存块的索引表,并不处理内存块中的内容。它在内存块的索引表中标识出这块内存处在free的状态,然后就返回。很多超级新手认为free会将内存块清0(或者其它的什么值),这是幻想。

同样的道理,如果你在函数中返回栈上的一个内存块的指针,在函数返回后仍然可以得到与在函数中一样的内容。这也是因为函数返回的时候,只是更改了栈指针,并没有人去管栈内的内容。当然前提是该函数返回后没有进行其它的函数调用,这样栈内内容就可以保持不变。但是一旦发生了函数调用,栈指针又向下压,栈内的内容就可能改变了。因此,绝对不能返回栈上的内存块指针,即使有的时候看上去很正确。

6.  linux编译的程序能不能在sun上跑

注1)  

如果你没有这样的例子,可以参照:

#include <stdio.h>;

int main()

{

    printf("hello world/n");

    return 0;

}

 

S 1.1 gift C\ C++程序

阅读数 883

Visual C++(MFC)实现C/S

阅读数 2551

C++ UDP C/S 简单封装

阅读数 2875

没有更多推荐了,返回首页