手把手写C++服务器:编译实操——打开gcc/g++世界
Posted 沉迷单车的追风少年
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手把手写C++服务器:编译实操——打开gcc/g++世界相关的知识,希望对你有一定的参考价值。
前言:前面几篇文章简单介绍了Linux C/C++编译原理、优化方法等(手把手写C++服务器(2):C/C++编译链接模型、函数重载隐患、头文件使用规范)、(手把手写C++服务器(3):C++编译常见问题、编译优化方法、C++库发布方式),这一篇一个简单的功能程序TTCP为例,介绍gcc/g++编译的具体用法和实战操作。
目录
GCC和G++有什么区别?
主要区别是调用的编译器不同, 并且传递给链接器的参数不同。
- g++ 会把
.c
文件当做是 C++ 语言 (在.c
文件前后分别加上-xc++
和-xnone
, 强行变成 C++), 从而调用cc1plus
进行编译. - g++ 遇到
.cpp
文件也会当做是 C++, 调用cc1plus
进行编译. - g++ 还会默认告诉链接器, 让它链接上 C++ 标准库.
- gcc 会把
.c
文件当做是 C 语言. 从而调用cc1
进行编译. - gcc 遇到
.cpp
文件, 会处理成 C++ 语言. 调用cc1plus
进行编译. - gcc 默认不会链接上 C++ 标准库.
GCC/G++编译步骤
1、预处理,生成 .i 的文件[预处理器cpp]
2、将预处理后的文件转换成汇编语言, 生成文件 .s [编译器egcs]
3、有汇编变为目标代码(机器代码)生成 .o 的文件[汇编器as]
4、连接目标代码, 生成可执行程序 [链接器ld]
GCC/G++参数说明
-ansi | 只支持 ANSI 标准的 C 语法。这一选项将禁止 GNU C 的某些特色, 例如 asm 或 typeof 关键词。 |
-c | 只编译并生成目标文件。 |
-DMACRO | 以字符串"1"定义 MACRO 宏。 |
-DMACRO=DEFN | 以字符串"DEFN"定义 MACRO 宏。 |
-E | 只运行 C 预编译器。 |
-g | 生成调试信息。GNU 调试器可利用该信息。 |
-IDIRECTORY | 指定额外的头文件搜索路径DIRECTORY。 |
-LDIRECTORY | 指定额外的函数库搜索路径DIRECTORY。 |
-lLIBRARY | 连接时搜索指定的函数库LIBRARY。 |
-m486 | 针对 486 进行代码优化。 |
-o FILE | 生成指定的输出文件。用在生成可执行文件时。 |
-O0 | 不进行优化处理。 |
-O 或 -O1 | 优化生成代码。 |
-O2 | 进一步优化。 |
-O3 | 比 -O2 更进一步优化,包括 inline 函数。 |
-shared | 生成共享目标文件。通常用在建立共享库时。 |
-static | 禁止使用共享连接。 |
-UMACRO | 取消对 MACRO 宏的定义。 |
-w | 不生成任何警告信息。 |
-Wall | 生成所有警告信息。 |
编译优化选项
-O 控制所有的优化等级。使用优化选项会使编译过程耗费更多的时间,并且占用更多的内存,尤其是在提高优化等级的时候。-O0表示没有优化,-O1为缺省值,-O3优化级别最高
-O0
这个等级(字母“O”后面跟个零)关闭所有优化选项,也是CFLAGS或CXXFLAGS中没有设置-O等级时的默认等级。这样就不会优化代码,这通常不是我们想要的。
O0选项不进行任何优化,在这种情况下,编译器尽量的缩短编译消耗(时间,空间),此时,debug会产出和程序预期的结果。当程序运行被断点打断,此时程序内的各种声明是独立的,我们可以任意的给变量赋值,或者在函数体内把程序计数器指到其他语句,以及从源程序中 精确地获取你期待的结果.
-O1
优化会消耗少多的编译时间,它主要对代码的分支,常量以及表达式等进行优化。
-O2
会尝试更多的寄存器级的优化以及指令级的优化,它会在编译期间占用更多的内存和编译时间。
-O3
在O2的基础上进行更多的优化,例如使用伪寄存器网络,普通函数的内联,以及针对循环的更多优化。
-Os
主要是对代码大小的优化,我们基本不用做更多的关心。 通常各种优化都会打乱程序的结构,让调试工作变得无从着手。并且会打乱执行顺序,依赖内存操作顺序的程序需要做相关处理才能确保程序的正确性。
-pipe
-pipe是个安全而有趣的标记。它对代码生成毫无影响,但是可以加快编译过程。此标记指示编译器在不同编译时期使用pipe而不是临时文件。
-fomit-frame-pointer
这是个用来削减代码尺寸的常用标记。在所有不影响除错(例如x86-64)的构架上,所有的-O等级(除了-O0)中都启用了它,但是也可能需要手动添加到你的标记中。尽管GNU gcc手册没有明确指出-O会启用这个标记的所有构架,你需要在x86上手动启用它。使用这个标记会使除错难以进行。
特别的,它会使排查Java程序的故障变得困难,尽管Java代码并不是唯一受此选项影响的代码。所以此标记虽然有用,但也会使除错变得困难,特别是backtrace将变得毫无用处。然而,你不准备做软件除错并且没有在CFLAGS中加入-ggdb之类与除错相关的标记的话,那就可以试试-fomit-frame-pointer。
重要: 请勿联合使用-fomit-frame-pointer和与之相似的-momit-leaf-frame-pointer。开启后者并无多大用处,因为-fomit-frame-pointer已经把事情搞定了。此外,-momit-leaf-frame-pointer还将降低代码性能。
安装boost
boost对于linux C++开发的重要性不言而喻,使用了boost要编译,必然要先安装boost库。
wget http://sourceforge.net/projects/boost/files/boost/1.54.0/boost_1_54_0.tar.gz tar -xzvf boost_1_54_0.tar.gz cd boost_1_54_0 ./bootstrap.sh --prefix=/usr/local ./b2 install --with=all boost库被安装在/usr/local/lib下面
添加路径:
g++ xxx.cpp -L/usr/local/lib -lboost_system
万事俱备,开始编译
下面我们以TTCP为例,编译命令如下:
g++ -std=c++11 -g -O2 Acceptor.cc InetAddress.cc TcpStream.cc Socket.cc ttcp.cc -o ttcp -lboost_program_options
命令解释:
- -std=c++11 表示使用C++11 标准。
- -g表示生成调试信息。
- -O2表示编译优化。O2级别的编译优化会尝试更多的寄存器级的优化以及指令级的优化,它会在编译期间占用更多的内存和编译时间。
- Acceptor.cc InetAddress.cc TcpStream.cc Socket.cc 表示编译依赖的源文件;ttcp.cc表示编译文件。
- -o ttcp表示生成指定输出文件。
- -lboost_program_options 表示指定的boost库。program_options用于使用命令行参数。
附上ttcp的源代码:
#include "Acceptor.h"
#include "InetAddress.h"
#include "TcpStream.h"
#include <iostream>
#include <boost/program_options.hpp>
#include <sys/time.h>
struct Options
{
uint16_t port;
int length;
int number;
bool transmit, receive, nodelay;
std::string host;
Options()
: port(0), length(0), number(0),
transmit(false), receive(false), nodelay(false)
{
}
};
struct SessionMessage
{
int32_t number;
int32_t length;
} __attribute__ ((__packed__));//紧凑排列的方式取消字节对齐
struct PayloadMessage
{
int32_t length;
//防止超长,用最后一个数组表示长度,此长度运行时决定
//分配多少内存,数组就有多长
char data[0];
};
double now()
{
struct timeval tv = { 0, 0 };
gettimeofday(&tv, NULL);//sys/time.h当中,精确获取当前时间
return tv.tv_sec + tv.tv_usec / 1000000.0;
}
// FIXME: rewrite with getopt(3).
bool parseCommandLine(int argc, char* argv[], Options* opt)
{
// 使用boost,命令行选项功能
namespace po = boost::program_options;
po::options_description desc("Allowed options");
desc.add_options()
("help,h", "Help")
("port,p", po::value<uint16_t>(&opt->port)->default_value(5001), "TCP port")
("length,l", po::value<int>(&opt->length)->default_value(65536), "Buffer length")
("number,n", po::value<int>(&opt->number)->default_value(8192), "Number of buffers")
("trans,t", po::value<std::string>(&opt->host), "Transmit")
("recv,r", "Receive")
("nodelay,D", "set TCP_NODELAY")
;
po::variables_map vm;
try {
po::store(po::parse_command_line(argc, argv, desc), vm);
} catch (...) {
std::cout << "input undecleare options!" <<std::endl;
}
po::notify(vm);
opt->transmit = vm.count("trans");
opt->receive = vm.count("recv");
opt->nodelay = vm.count("nodelay");
if (vm.count("help"))
{
std::cout << desc << std::endl;
return false;
}
if (opt->transmit == opt->receive)
{
printf("either -t or -r must be specified.\\n");
return false;
}
printf("port = %d\\n", opt->port);
if (opt->transmit)
{
printf("buffer length = %d\\n", opt->length);
printf("number of buffers = %d\\n", opt->number);
}
else
{
printf("accepting...\\n");
}
return true;
}
// 客户端代码
void transmit(const Options& opt)
{
InetAddress addr(opt.port);
if (!InetAddress::resolve(opt.host.c_str(), &addr))
{
printf("Unable to resolve %s\\n", opt.host.c_str());
return;
}
printf("connecting to %s\\n", addr.toIpPort().c_str());
TcpStreamPtr stream(TcpStream::connect(addr));//移动语义
if (!stream)
{
printf("Unable to connect %s\\n", addr.toIpPort().c_str());
perror("");
return;
}
if (opt.nodelay)
{
stream->setTcpNoDelay(true);
}
printf("connected\\n");
double start = now();
struct SessionMessage sessionMessage = { 0, 0 };
sessionMessage.number = htonl(opt.number);
sessionMessage.length = htonl(opt.length);
if (stream->sendAll(&sessionMessage, sizeof(sessionMessage)) != sizeof(sessionMessage))
{
perror("write SessionMessage");
return;
}
const int total_len = sizeof(int32_t) + opt.length;
PayloadMessage* payload = static_cast<PayloadMessage*>(::malloc(total_len));
std::unique_ptr<PayloadMessage, void (*)(void*)> freeIt(payload, ::free);
assert(payload);
payload->length = htonl(opt.length);
for (int i = 0; i < opt.length; ++i)
{
payload->data[i] = "0123456789ABCDEF"[i % 16];
}
double total_mb = 1.0 * opt.length * opt.number / 1024 / 1024;
printf("%.3f MiB in total\\n", total_mb);
for (int i = 0; i < opt.number; ++i)
{
int nw = stream->sendAll(payload, total_len);
assert(nw == total_len);
int ack = 0;
int nr = stream->receiveAll(&ack, sizeof(ack));
assert(nr == sizeof(ack));
ack = ntohl(ack);
assert(ack == opt.length);
}
double elapsed = now() - start;
printf("%.3f seconds\\n%.3f MiB/s\\n", elapsed, total_mb / elapsed);
}
void receive(const Options& opt)
{
Acceptor acceptor(InetAddress(opt.port));
TcpStreamPtr stream(acceptor.accept());
if (!stream)
{
return;
}
struct SessionMessage sessionMessage = { 0, 0 };
if (stream->receiveAll(&sessionMessage, sizeof(sessionMessage)) != sizeof(sessionMessage))
{
perror("read SessionMessage");
return;
}
sessionMessage.number = ntohl(sessionMessage.number);
sessionMessage.length = ntohl(sessionMessage.length);
printf("receive buffer length = %d\\nreceive number of buffers = %d\\n",
sessionMessage.length, sessionMessage.number);
double total_mb = 1.0 * sessionMessage.number * sessionMessage.length / 1024 / 1024;
printf("%.3f MiB in total\\n", total_mb);
const int total_len = sizeof(int32_t) + sessionMessage.length;
PayloadMessage* payload = static_cast<PayloadMessage*>(::malloc(total_len));
std::unique_ptr<PayloadMessage, void (*)(void*)> freeIt(payload, ::free);
assert(payload);
double start = now();
for (int i = 0; i < sessionMessage.number; ++i)
{
payload->length = 0;
//验证两个消息长度是否相等
if (stream->receiveAll(&payload->length, sizeof(payload->length)) != sizeof(payload->length))
{
perror("read length");
return;
}
payload->length = ntohl(payload->length);
// 不相等直接崩溃退出
assert(payload->length == sessionMessage.length);
if (stream->receiveAll(payload->data, payload->length) != payload->length)
{
perror("read payload data");
return;
}
// 读取完成之后构造响应,再发送回去
int32_t ack = htonl(payload->length);
if (stream->sendAll(&ack, sizeof(ack)) != sizeof(ack))
{
perror("write ack");
return;
}
}
double elapsed = now() - start;
printf("%.3f seconds\\n%.3f MiB/s\\n", elapsed, total_mb / elapsed);
}
int main(int argc, char* argv[])
{
Options options;
if (parseCommandLine(argc, argv, &options))
{
if (options.transmit)
{
transmit(options);
}
else if (options.receive)
{
receive(options);
}
else
{
assert(0);
}
}
}
参考:
- https://www.zhihu.com/question/20940822
- https://www.runoob.com/w3cnote/gcc-parameter-detail.html
- https://www.zhihu.com/question/20940822
- https://www.runoob.com/w3cnote/gcc-parameter-detail.html
- https://blog.csdn.net/wteruiycbqqvwt/article/details/89377528
- https://www.linuxprobe.com/linux-boost.html
以上是关于手把手写C++服务器:编译实操——打开gcc/g++世界的主要内容,如果未能解决你的问题,请参考以下文章
手把手写C++服务器:C++编译常见问题编译优化方法C++库发布方式
手把手写C++服务器:C/C++编译链接模型函数重载隐患头文件使用规范