《Effective Modern C++》读后感

前言

前几天学校图书馆进了一批新书,看了下,刚好有《Effective Modern C++》这本书,这本书一直想看,于是借来看了下。(PS:总共三本,一开始还是借不到的,只能预约,蛮意外的,原来我们学校有这么多人学C++吗,毕竟班里其他人都学的是Java……

开头

献给爱犬,嗯,不过这次少了张图。关于C++11/14,在我之前写一个项目里用过并熟悉了下,所以这本书除了并发那里基础知识都没什么问题,那么来看看C++11/14又有什么坑呢?

型别推导

推导,这一听就是模板了,虽然日常编码中模板用得是比较少的,不过看一看还是很有意思的:

  • C++11中,模板型别推导多了一个万能引用 T&&,并且我还学到了数组和函数的指针退化,并且在引用情况下并不会退化;
  • auto和模板推导基本一致,除了大括号表达式;
  • decltype,恰如其名,就是准确推断类型,用来修饰auto就可以改变推导规则;
  • 查看推导结果,这还是信不过编译器吧,不过有时候这也是没办法的;
阅读更多
《链接、装载与库》——运行库

栈和函数调用惯例

栈是很重要的一种数据结构,调用函数,创建局部变量都会压栈,函数究竟是怎么调用的呢,通常是将函数参数压栈,将下一条指令地址压栈,然后跳转到函数体内执行,函数体内大概是这样:

1
2
3
4
5
6
7
8
9
10
11
push ebp        // 保存ebp
mov ebp, esp // 重新设置ebp
sub esp x // 分配栈上临时空间
[push reg] // 保存寄存器
//
... // 实际函数内容
//
[pop reg] // 恢复寄存器
mov esp, ebp // 回收栈临时空间
pop ebp // 恢复ebp
ret // 函数返回

但,函数调用还有一些问题:被调用函数是怎么知道函数参数的位置的?这些函数参数由谁负责清理?这需要一些约定,而这个约定就是函数调用惯例。通常来说,函数调用惯例包括下面几个方面:

  • 函数参数的传递顺序和方式;
  • 栈的维护方式;
  • 名字修饰的方法;

几种常见的调用惯例:

阅读更多
《链接、装载与库》——动态装载

进程的装载

从操作系统角度来看,进程的装载大概是:

  • 创建虚拟地址空间,分配一个页目录,创建相关的数据结构;
  • 读取可执行文件头,建立虚拟空间与可执行文件的映射关系;
  • 将CPU指令寄存器设置为可执行文件入口地址,开始执行;

进程虚拟地址空间分布

ELF分为链接视图和执行视图,链接视图就是静态链接那里讲的,但我们知道,现在操作系统都是以页机制管理内存的,如果一个section一页,太浪费内存,因此需要把相同权限(RWX)的section当做一个segment进行虚拟地址映射,这就是ELF的两种视图。

至于空间分布,大概如图:

可以cat /proc/pid/maps来查看。

阅读更多
《链接、装载与库》——静态链接

ELF文件格式

ELF是Linux下的文件格式,可分为:

  • 可执行文件(bin);
  • 可重定位文件(.o, .a);
  • 共享目标文件(.so);
  • 核心转储文件(coredump);

ELF文件结构:ELF文件头,各种section(.text, .data, .bss等),各种表(section表、symbol表、string表等)。文件头里存放了各种元数据,比如ELF类型、入口地址、表偏移、段数量等;各种section即为代码和数据;section表存放的是各section的信息,比如类型、名字、偏移、大小等;string表里存放着ELF中的字符串;rel表和symbol表是链接中很重要的结构,因为链接本质就是”填空”,在哪里填空,怎么填空,都依赖于这两张表。

符号相关

在链接中,函数和变量统称为符号,因此可知,每个目标文件都可以定义符号或引用符号。从链接角度来看,符号也有很多类型,局部符号(static)、全局符号(全局变量、函数)、外部符号(引用的外部符号)、特殊符号(ld根据链接脚本添加)。关于名字,符号名通常与变量、函数名不一致,这称为符号修饰,这也是extern “C”的原因。

强符号和弱符号:默认的,函数和初始化全局变量为强符号,未初始化全局变量为弱符号,规则如下:

  • 强符号不能重复多次定义,否则链接报错;
  • 一个强符号和多个弱符号,链接器选择强符号;
  • 都是弱符号,选择占用空间最大的那个;
阅读更多
Go学习笔记

语法

  • for是Go中的while;
  • if可以在判断之前执行一条语句;
  • switch自动break,case无需为整数;
  • 切片的cap就是切片能扩展的最大长度;
  • 指针不能运算;
  • 类型和函数不用先声明就能用;
  • Go里面没有隐式类型提升;
  • 类型定义和类型方法必须在同一个包内;
  • .(type)类型选择必须和switch一起结合使用(泛型编程?);
  • 无论是指针还是值,都可以直接通过 . 访问成员,Go自动(*p).x
  • 类型方法也是,无论接受指针还是值,指针和值都可以直接调用,Go自动(*p).foo()或(&a).foo(),这时的指针接受者可以理解为C++里的引用
  • 每定义一个常量,iota就递增1,iota从0开始;

接口

  • 接口指针永远无法满足接口
  • 接口里存的永远是变量的具体类型,绝不可能是另一个接口类型,因此可以把接口类型和其他类型看做两种类型,接口是用来存其他类型的
  • 实现接口时如果是指针类型,那么就只有类型指针满足接口,毕竟指针接口会更改原值
  • 但实现接口时如果是值类型,那么类型和类型指针都满足接口,这时只需要传一个拷贝,那么是不是指针就无所谓了
  • 空接口interface{}可保存任意值;
  • 接口类型可以内嵌其他接口,相当于把其他接口方法写在这里;
阅读更多
从一道面试题入手——谈谈socket的关闭
服务器端创建一个socket,然后listen之后sleep,如果在sleep期间,有客户端connect服务器,服务器是否会醒来?或者怎样?

让我们来做个实验,代码如下(使用windz网络库):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// server.cpp
int main(int argc, char **argv)
{
Socket sockfd(socket(AF_INET, SOCK_STREAM, 0));
sockfd.Bind(InetAddr(2019));
sockfd.Listen();
sleep(20);
}

// client.cpp
int main(int argc, char **argv)
{
Socket sockfd(socket(AF_INET, SOCK_STREAM, 0));
sockfd.Connect(InetAddr("127.0.0.1", 2019));
sleep(10);
}
阅读更多
谈谈写windz的收获

项目地址:Crystalwindz/windz

经过一个半月的努力,windz库终于写完了。起初只是想做个Web服务器交课设,但这服务器越写越偏,越来越像一个网络库,于是就想,索性就写成网络库吧,都说C++程序员都喜欢造轮子,也许我也是。

一开始写Web服务器时,我也只是看过APUE、UNP,简单了解网络编程的人,实际写起来C++网络程序,也是相当困难,无从下手。万事开头难嘛,于是尝试在github上找找别人写的Web服务器,阅读源码来学习一下,有幸看到了linyacool/WebServer,readme写的相当详细,并大力推荐了陈硕的chenshuo/muduo和他的Linux多线程服务端编程,我看到后当即就去学校图书馆借了一本。借来这本书,看到目录和书背上的一些问题,感觉无比痛快,我的疑问大部分都在上面有了详细的解答,上一次看书这么爽还是看Effective C++的时候。

于是便读了书,跟着书中的指引和源码,一步一步地把windz这个库写完了,通过读书和写这个库,我学到了太多太多:

阅读更多
Redis设计与实现-数据库与持久化

键空间

Redis是一个key-value数据库,RedisServer中有多个数据库,每个数据库都由一个redisDb结构表示,其中的dict字典保存了数据库中所有的键值对,dict称之为键空间:

  • key就是数据库的键;
  • value就是数据库的值,具体为字符串、列表、哈希、集合、有序集合中的一种;

假设这是一个现有的键空间:

添加键:

阅读更多