手把手写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++有什么区别?

GCC/G++编译步骤

GCC/G++参数说明

编译优化选项

-O0

-O1

-O2

-O3

-Os

-pipe

-fomit-frame-pointer

安装boost

万事俱备,开始编译

参考:


GCC和G++有什么区别?

主要区别是调用的编译器不同, 并且传递给链接器的参数不同。

  1. g++ 会把 .c 文件当做是 C++ 语言 (在 .c 文件前后分别加上 -xc++-xnone, 强行变成 C++), 从而调用 cc1plus 进行编译.
  2. g++ 遇到 .cpp 文件也会当做是 C++, 调用 cc1plus 进行编译.
  3. g++ 还会默认告诉链接器, 让它链接上 C++ 标准库.

  1. gcc 会把 .c 文件当做是 C 语言. 从而调用 cc1 进行编译.
  2. gcc 遇到 .cpp 文件, 会处理成 C++ 语言. 调用 cc1plus 进行编译.
  3. 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);
    }
  }
}

参考:

以上是关于手把手写C++服务器:编译实操——打开gcc/g++世界的主要内容,如果未能解决你的问题,请参考以下文章

手把手写C++服务器:C++编译常见问题编译优化方法C++库发布方式

手把手写C++服务器:专栏文章-汇总导航更新中

手把手写C++服务器:C/C++编译链接模型函数重载隐患头文件使用规范

手把手写C++服务器:永远滴神vim(源码安装插件管理颜色主题代码高亮快捷键设置搜索替换环境保护)

手把手写C++服务器(27):五大文件描述符零拷贝控制总结

手把手写C++服务器(26):常用I/O操作创建文件描述符