rust学习笔记(c++ based)

为什么又要写一个?不写一遍真的记不住,写的也没有参考中的文章写得好。详细的版本直接跳到参考。

rust这个语言真是热闹。很多特性的融合,对标c++,必须要探究一下! 首先,这个社区+api文档 + 手把手demo 真的非常好,上手难度还好。写好的程序不好说,这文档是真方便,随便点一下就跳到实现上了。 这也说明新语言没那么大包袱。c++打开头文件看实现,天书,还是点开cppreference看接口文档吧。这东西和cppcoreguideline文档也是新搞的。c++也有人注意到了这个问题吧。

列几点我看书(rust book)过程中记录的好玩的地方。

  • 默认不可变语义,这个和c++是正好相反的,c/c++ 想要保证不可变,程序员自觉不改,或者加const,加上const也会有阴招(const_cast)绕过去,c/c++原则,充分信任程序员,程序员要知道自己在做什么,rust不信任,默认不可变,可变得手动标mut,不然分分钟报错
  • 引用语义。c++中,值语义,引用语义的切换非常自然。这个代价就是类型转换天花乱坠,但是自然。rust的引用语义(以及衍生的slice)需要显式指定,传参数得带&,这语法太难看。
  • 接上面,引用在rust中有借用语义,编译器强保证,所以用起来会很痛苦。
  • 宏,rust的宏有点像c++的变参模板,但是语法很邪恶,像perl shell这种脚本语法。很邪恶。
  • enum,这个东西就是个std::variant,配合rust本身的match语法,更变态一些。match这东西从函数式语言中抄过来的。也是先进性体现了。
  • traits 好东西,和c++的type_traits也有点像,也像c++ concept这种静态接口。rust直接去掉了继承,大家实现接口就好了,or enum
  • 这个函数声明风格挺像go的。说起go,go也有interface这种东西,也有magic方法,magic方法有点不适应,就比如make defer这种,没规律。难受。感觉还是c风格,指针回调的感觉。相比rust的traits方法还算系统,c++ concept当初也是这个计划,
  • 最后一句可以当返回值,这是ruby perl语言的优势,不用return。这个有利有弊
  • 很多语法和go很像,但是更系统一些(或者是我对go有偏见)

暂时就这么多。具体还是要写代码才懂。

reference

  • 一个英语博客 https://github.com/nrc/r4cppp
  • 一个不错的总结,针对c++语法 写的不错,基本把我想写的写了。
    • https://xr1s.me/2018/03/01/rust-learning-notes-for-cxx-programmer-part-one/
    • https://xr1s.me/2018/03/01/rust-learning-notes-for-cxx-programmer-part-two/
    • https://xr1s.me/2018/03/01/rust-learning-notes-for-cxx-programmer-part-three/

看到这里或许你有建议或者疑问或者指出我的错误,请留言评论或者邮件mailto:wanghenshui@qq.com, 多谢!

觉得写的不错可以点开扫码赞助几毛 微信转账
Read More

Apache Kafka源码剖析笔记



特点

  • Kafka具有近乎实时性的消息处理能力,即使面对海量消息也能够高效地存储消息和查询消息。
  • Kafka将消息保存在磁盘中,在其设计理念中并不惧怕磁盘操作,它以顺序读写的方式访问磁盘, 从而避免了随机读写磁盘导致的性能瓶颈。
  • Kafka支持批量读写消息,并且会对消息进行批量压缩,这样既提高了网络的利用率,也提高了压 缩效率。
  • Kafka支持消息分区,每个分区中的消息保证顺序传输,而分区之间则可以并发操作,这样就提高 了Kafka的并发能力。
  • Kafka也支持在线增加分区,支持在线水平扩展。
  • Kafka支持为每个分区创建多个副本,其中只会有一个Leader副本负责读写,其他副本只负责与 Leader副本进行同步,这种方式提高了数据的容灾能力。Kafka会将Leader副本均匀地分布在集群 中的服务器上,实现性能最大化

为什么引入mq中间件,以及,原有方案的弊端

  • 由于子系统之间存在的耦合性,两个存储之间要进行数据交换的话,开发人员就必须了解这两个 存储系统的API,不仅是开发成本,就连维护成本也会很高。一旦其中一个子系统发生变化,就可 能影响其他多个子系统,这简直就是一场灾难。
  • 在某些应用场景中,数据的顺序性尤为重要,一旦数据出现乱序,就会影响最终的计算结果,降 低用户体验,这就提高了开发的难度。
  • 除了考虑数据顺序性的要求,还要考虑数据重传等提高可靠性的机制,毕竟通过网络进行传输并 不可靠,可能出现丢失数据的情况。
  • 进行数据交换的两个子系统,无论哪一方宕机,重新上线之后,都应该恢复到之前的传输位置, 继续传输。尤其是对于非幂等性的操作,恢复到错误的传输位置,就会导致错误的结果。
  • 随着业务量的增长,系统之间交换的数据量会不断地增长,水平可扩展的数据传输方式就显得尤 为重要。

应对的解决方案

  • 中间件解耦
  • 数据持久化
  • 扩展与容灾
    • topic,partition,replica
    • 多个consumer消费,各自记录消费标签信息,由consumer决定cursor

核心概念

  • 消息
  • topic/partition/log
    • partition offset保证顺序性
    • partition 用Log表述,大小有限,可以分节,顺序io追加,不会有性能问题
      • 索引文件 稀疏索引 内存中定位加速
    • 保留策略 顺序写总会写满,设置消息保留时间或者总体大小
    • 日志压缩 类似rocksdb compaction
  • Broker kafka server概念,接受生产者的消息,分配offset保存到磁盘中
  • 副本(并不是实时同步)
    • ISR集合,可用的没有lag太多的可升leader的节点的集合
    • HighWatermar LEO,描述可消费的属性
      • ①Producer向此Partition推送消息。
      • ②Leader副本将消息追加到Log中,并递增其LEO。
      • ③Follower副本从Leader副本拉取消息进行同步。
      • ④Follower副本将拉取到的消息更新到本地Log中,并递增其LEO。
      • ⑤当ISR集合中所有副本都完成了对offset=11的消息的同步,Leader副本会递增HW。
      • 在①~⑤步完成之后,offset=11的消息就对生产者可见了。
  • 消费者,消费组、

image-20200914163714353

细心的读者可能会问,为什么GZIP压缩方式会直接使用new创建,而Snappy则使用反射方式呢?这主要是因为GZIP使用的GZIPOutputStream是JDK自带的包,而Snappy则需要引入额外的依赖包,为了在不使用Snappy压缩方式时,减少依赖包,这里使用反射的方式动态创建。这种设计的小技巧,值得读者积累。在Compressor中还提供了wrapForInput()方法,用于创建解压缩输入流,逻辑与wrapForOutput()类似,不再赘述。

生产者KafkaProducer分析

同步还是异步就差在future本身调用不调用get

image-20200916095525486

结构很清晰

KafkaProducer

  • send()方法:发送消息,实际是将消息放入RecordAccumulator暂存,等待发送。
  • flush()方法:刷新操作,等待RecordAccumulator中所有消息发送完成,在刷新完成之前会阻塞调用的线程。
  • partitionsFor()方法:在KafkaProducer中维护了一个Metadata对象用于存储Kafka集群的元数据,Metadata中的元数据会定期更新。partitionsFor()方法负责从Metadata中获取指定Topic中的分区信息。
    • topic, verison, timestamp….
  • close()方法:关闭此Producer对象,主要操作是设置close标志,等待RecordAccumulator中的消息清 空,关闭Sender线程

RecordAccumulator

RecordAccumulator中有一个以TopicPartition为key的ConcurrentMap,每个value是ArrayDeque<RecordBatch>(ArrayDeque并不是线程安全的集合,后面会详细介绍其加锁处理过程),其中缓存了发往对应TopicPartition的消息。每个RecordBatch拥有一个MemoryRecords对象的引用

(1)Deque中有多个RecordBatch或是第一个RecordBatch是否满了。 (2)是否超时了。 (3)是否有其他线程在等待BufferPool释放空间(即BufferPool的空间耗尽了)。 (4)是否有线程正在等待flush操作完成。 (5)Sender线程准备关闭。

BufferPool

  • 有锁。全局分配器

Sender

据RecordAccumulator的缓存情况,筛选出可以向哪些Node节点发送消息,RecordAccumulator.ready();然后,根据生产者与各个节点的连接情况(由NetworkClient管理),过滤Node节点;之后,生成相应的请求,这里要特别注意的是,每个Node节点只生成一个请求;最后,调用NetWorkClient将请求发送出去

image-20200921165610705

controller

http://www.sirann.cn/blog/kafka-controller-%E6%A8%A1%E5%9D%97%E4%B8%80%E6%A6%82%E8%BF%B0/

删除消息,支持按照offset SO,按照topic来删

支持时间删除和大小删除

痛点

对于单纯运行Kafka的集群而言,首先要注意的就是为Kafka设置合适(不那么大)的JVM堆大小。从上面的分析可知,Kafka的性能与堆内存关系并不大,而对page cache需求巨大。根据经验值,为Kafka分配6~8GB的堆内存就已经足足够用了,将剩下的系统内存都作为page cache空间,可以最大化I/O效率。

另一个需要特别注意的问题是lagging consumer,即那些消费速率慢、明显落后的consumer。它们要读取的数据有较大概率不在broker page cache中,因此会增加很多不必要的读盘操作。比这更坏的是,lagging consumer读取的“冷”数据仍然会进入page cache,污染了多数正常consumer要读取的“热”数据,连带着正常consumer的性能变差。在生产环境中,这个问题尤为重要。

解决方案,调整page cache https://www.jianshu.com/p/92f33aa0ff52

实时消费与延迟消费的作业在 PageCache 层次产生竞争,导致实时消费产生非预期磁盘读。线上存在 20%的延迟消费作业,污染cache

传统 HDD 随着读并发升高性能急剧下降。

解决方案

  • 引入SSD 做cache https://www.infoq.cn/article/k6dqfqqihpjfepl3y3hs
  • 重新设计kafka cache系统 https://www.jiqizhixin.com/articles/2019-07-23-11

优化细节

zero-copy技术

减少甚至避免用户空间和内核空间之间的数据拷贝:在一些场景下,用户进程在数据传输过程中并不需要对数据进行访问和处理,那么数据在 Linux 的 Page Cache 和用户进程的缓冲区之间的传输就完全可以避免,让数据拷贝完全在内核里进行,甚至可以通过更巧妙的方式避免在内核里的数据拷贝。这一类实现一般是通过增加新的系统调用来完成的,比如 Linux 中的 mmap(),sendfile() 以及 splice()

这里用的sendfile


看到这里或许你有建议或者疑问或者指出我的错误,请留言评论或者邮件mailto:wanghenshui@qq.com, 多谢!

觉得写的不错可以点开扫码赞助几毛 微信转账
Read More

memcached源码剖析笔记



memcache特点

  • 协议简单
    • add/set/replace/get/get/delete
  • 基于libevent
  • 内存存储,LRU,抗干扰?

内存存储机制

slab allocation,感觉像linux系统的slab

  • page 1M chunk内存空间slab 特定大小的chunk组
  • 解决内存碎片但是空间利用存在浪费
    • chunk可以动态増缩?
  • 增长因子可以设定 growth factor

image-20200911173653707


ref


看到这里或许你有建议或者疑问或者指出我的错误,请留言评论或者邮件mailto:wanghenshui@qq.com, 多谢!

觉得写的不错可以点开扫码赞助几毛 微信转账
Read More

infusion介绍

infusion介绍

度量图

与业界项目代码相比 红色偏高,蓝色正常,绿色偏低

CYCLO 圈复杂度 FOUT 方法扇出次数
LOC 代码行 CALL 方法调用次数(扇入)
NOM method的数量 NOM method的数量
NOC Class的数量  
NOP package数量(C/C++语言中为目录的数量)  

顶部: NDD, HIT分别是指类的平均继承宽度平均继承深度

平均继承宽度: 类的继承树上,子类占所有类的比例。 默认合理范围0.2~0.6

平均继承深度: 类的继承树上,继承的层次,多棵继承树取平均值。

默认合理范围0.1~0.3

这两个指标的导向是:少用继承,用继承的时候要控制深度和宽度,减少复杂性。

软件中支持的24种代码的坏味道

编号 设计缺陷名称 编号 设计缺陷名称
1 Blob Class 13 Refused Parent Bequest
2 Blob Module 14 SAP Breaker
3 Cyclic Dependencies 15 Schizophrenic Class
4 Data Class 16 Schizophrenic Module
5 Data Clumps 17 Shotgun Surgery
6 Data Module 18 Significant External Duplication
7 Distorted Hierarchy 19 Significant Internal Duplication
8 Feature Envy 20 Significant Sibling Duplication
9 God Class 21 Tradition Breaker
10 God Module 22 Underutilized Interface
11 Intensive Coupling 23 Unnecessary Coupling
12 Message Chains 24 Unstable Dependencies

InFusion的度量是以 Size & Complexity (代码规模与复杂度)、Encapsulation(封装性)、Coupling(耦合)Hierarchies(继承性) Cohesion(内聚性)五个设计属性来度量的

###subsystems子系统类级缺陷

Cyclic Dependencies (循环依赖)

循环依赖 例如:各个子系统互相依赖形成一个或多个环。

子系统间互相依赖。编译不方便。

维护或重用(reuse)某子系统时,容易影响其他子系统

Unstable Dependencies (不稳定依赖)

不稳定依赖 例如:一个子系统(不稳定因子A)依赖另一个比它更不稳定(不稳定因子B)的子系统,A<B;判定稳定直观理解:一个系统多大程度依赖其它的系统

SAP Breakers (破坏SAP设计原则)

破坏了 Stable Abstractions Principle 稳定依赖原则

files文件级缺陷

Unnecessary Coupling (不必要的耦合)

例如:包含某个头文件,但是没有使用头文件中的任何内容。

包含了某些头文件却没有使用里面的内容。

这种不必要的包含会导致编译链接速度变慢。

Classes 类级缺陷

类型的设计缺陷只适合 C++语言。

Tradition Breaker:

子类隐藏了父类的功能。

Refused Parent Bequest:

子类不使用父类的遗产。多指两个类的继承关系使用混乱。

God Class(上帝类)

模块本身比较复杂且缺乏内聚,大量使用外部数据,控制过多外部功能

对其他模块/类的数据大量使用, 这样就破坏了模块的封装性

往往集中了许多模块的功能函数,从而加大了代码的耦合。

坏处是往往模块大而复杂,内聚性差,从而不好维护。

Data Class(数据类)

纯数据结构(c文件/c++为类),与功能的封装不紧密。

Blob Module:模块(c文件/C++类)有很多大而复杂的函数,关注模块内部的内聚和复杂性

Blob class(复杂类)

巨大臃肿的函数/文件/模块/类,都是糟糕的设计。

简言之,就是规模过大,圈复杂度过高,嵌套层次太深。

Modules 模块级缺陷

Data Module (数据模块)

纯数据结构(c文件/c++为类),与功能的封装不紧密。

Blob Module:模块(c文件/C++类)有很多大而复杂的函数,关注模块内部的内聚和复杂性

God Module (上帝模块)

全能(上帝)模块(c文件)。 模块本身比较复杂且缺乏内聚,大量使用外部数据,控制过多外部功能

对其他模块/类的数据大量使用, 这样就破坏了模块的封装性

God Class God module往往集中了许多模块的功能函数,从而加大了代码的耦合。

God Class God module坏处是往往模块大而复杂,内聚性差,从而不好维护。

Schizophrenic Module (紊乱的模块)

紊乱的模块。一个模块提供几组完全不相关的接口,接口没有按照功能进行内聚。

对代码模块或类的设计来说,好的风格应该是低耦合,高内聚。

模块内部应该功能单一。

如果一个模块或类实现了多个不同的功能,比如处理界面的模块又处理了很多业务相关功能,

那理应把它分成几个功能单一的类

Underutilized Interface(未用到的接口)

接口未被充分利用。 例如:一个模块有很多全局函数,但是有一半以上没有被其它模块使用。

模块提供的接口,即全局函数,没有被其他模块使用。

其他模块不使用的接口,不建议‘预先提供’

由于在C语言里,函数默认都是全局函数(允许外部模块调用),

所以公司很多C代码里的模块有很多内部使用的函数,却被认为是‘没被使用’的‘接口’。

这种情况可以忽略。

Significant Sibling Duplication (兄弟类中的代码重复)

同父类的子类之间的方法重复度很高

Functions 方法级缺陷

Blob Operation:(复杂的函数)

函数过于复杂

Data Clumps(数据泥团)

相似的函数参数列表,多次传递,没有封装成一个结构

数据虫。 比如这几个函数,可以看到几个参数总是共同出现。

可以想到,这几个参数间之间一定有内在的联系。

存在内在联系,则应该将这些参数封装在一起组成类或者结构。以类或者结构为传递的参数。

另外,参见《代码整洁之道》,函数的入参应该尽量少。

在业界大师看来,动不动3个4个以上的入参实在是太多了。

Feature Envy(特性依恋)

函数很少访问自己模块的数据,总是访问外部模块的数据。

Classes and modules 应该封装 data and the operations在一起

如果一个module里的函数总是访问其他module里的data ,

那就是这个函数没有和本module的数据封装在一起,违反了原则。

按照封装的原则,应该把这个函数移到他经常访问的那个module里面去。

Intensive Coupling(高耦合)

一个方法访问了很多其它的外部类或模块,同时调用这些类或模块的的方法很少

根据封装内聚的原则,模块之间应该低耦合,联系应该通过接口来完成。

Significant Internal Duplication(内部代码重复)

类或模块内的方法的重复度很高

Significant External Duplication(外部代码重复)

类或模块之间方法的重复度很高

Shotgun Surgery (散弹式调用)

修改一个问题,需要同时修改多处代码;散弹式的

Message Chains:(消息链)

需要经过多次的中间的传递和调用才能获得需要的数据,句柄、中间变量传递过多

Read More

nginx源码剖析笔记


linux内核参数优化

;最大句柄数
fs.file-max = 99999 
;time_wait状态的socket重新用于新的tcp链接
net.ipv4.tcp_tw_reuse = 1
;tcp发送keeptime的时间,调小可以快速清除无效连接(?单位是什么)
net.ipv4.tcp_keepalive_time = 600
;服务器主动关闭保持FIN_WAIT_2的最大时间
net.ipv4.tcp_fin_timeout = 30
;TIME_WAIT的socket最大值,上限,超过这个值会清掉所有TIME_WAIT TIME_WAIT过多会卡
net.ipv4.tcp_max_tw_buckets = 5000
net.ipv4.ip_local_port_range = 1024 61000
net.ipv4.tcp_rmem = 4096 32768 262142
net.ipv4.tcp_wmem = 4096 32768 262142
;内核处理接收包队列的长度上限
net.core.netdev_max_backlog = 8096
net.core.rmem_default = 262144
net.core.wmem_default = 262144
net.core.rmem_max = 2097152
net.core.wmem_max = 2097152
;tcp syn攻击
net.ipv4.tcp_syncookies =1
;正在三次握手建立阶段的请求队列,可以调高以免丢失客户端连接
net.ipv4.tcp_max_syn.backlog = 1024

滑动窗口大小与套接字缓存设置会在一定程度上影响并发 每个tcp链接都会为了维护滑动窗口而消耗内存

命令行相关

快速退出进程

kill -s SIGTERM <pid>

kill -s SIGINT <pid>

优雅退出

kill -s SIGQUIT <master pid>

kill -s SIGWINCH <worker pid>

重读配置

kill -s SIGHUP <master pid>

日志回滚

kill -s SIGUSR1 <master pid>

还真有这么实现日志回滚的。。。我惊了

平滑升级

kill -s SIGUSR2 <master pid>

这些是对信号的handler做自定义了

性能调优

  • 指定worker个数
    • worker绑核 (代码层怎么实现的?)
  • SSL硬件加速
  • 系统调用gettimeofday执行频率 (现在开销没那么大,也可以限制)
  • worker优先级 nice值设定

事件类配置型

  • 是否打开accept锁 连接负载均衡锁
    • 延迟时间设定

Nginx基础架构

  • 通用的ngx_module_t

  • TCP_DEFER_ACCEPT以及post_accept_timeout 如果连接很久没事件就踢掉

  • ngx_cycle_t

    • void ****conf_ctx 所有模块配置项结构体指针(数组->指针->指针数组->指针)

    image-20200910174704792

讲了很多模块知识。。我对模块不太感兴趣。不看了先


ref

  • 关键字 陶辉的博客。

看到这里或许你有建议或者疑问或者指出我的错误,请留言评论或者邮件mailto:wanghenshui@qq.com, 多谢!

觉得写的不错可以点开扫码赞助几毛 微信转账
Read More

overloaded trick

why

这篇就是参考链接2的总结,还是从参考链接1中单独拎出来说一下


之前学std::variant 和 std::visit 学到了overloaded这个模板,

#include <variant>
#include <cstdio>
#include <vector>

template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; }; // (1)
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;  // (2)

using var_t = std::variant<int, const char*>;

int main() {
    std::vector<var_t> vars = {1, 2, "Hello, World!"};

    for (auto& v : vars) {
        std::visit(overloaded {  // (3)
            [](int i) { printf("%d\n", i); },
            [](const char* str) { puts(str); }
        }, v);
    }

    return 0;
}

(2)是c++17引入的新特性,乍一看看不懂,咱们一点一点顺一下

首先,这个overloaded模板就是一个转发继承而来的operator (), 一个粗暴的版本,需要基类实现operator()

struct PrintInt { //(1)
    void operator() (int i) {
        printf("%d\n", i);
    }
};

struct PrintCString { // (2)
    void operator () (const char* str) {
        puts(str);
    }
};

struct Print : PrintInt, PrintCString { // (3)
    using PrintInt::operator();
    using PrintCString::operator();
};

如果写成模板形式,那就是

template <class... Ts> // (1)
struct Print : Ts... {
    using Ts::operator()...;
};

int main() {
    std::vector<var_t> vars = {1, 2, "Hello, World!"};

    for (auto& v : vars) {
        std::visit(Print<PrintCString, PrintInt>{}, v); // (2)
    }

    return 0;
}

注意到这个写法特别重,考虑到开头这个优雅的用法,使用lambda,代码写起来就更难看了

int main() {
    std::vector<var_t> vars = {1, 2, "Hello, World!"};
    auto PrintInt = [](int i) { printf("%d\n", i); }; // (1)
    auto PrintCString = [](const char* str) { puts(str); };

    for (auto& v : vars) {
        std::visit(
            Print<decltype(PrintCString), decltype(PrintInt)>{PrintCString, PrintInt}, // (2)
            v);
    }

    return 0;
}

所以理所当然,推导动作应该放在一个helper函数里, 上面这个调用模式还是很容写出一个推导helper的

template <class... Ts> // (1)
auto MakePrint(Ts... ts) {
    return Print<Ts...>{ts...};
}

int main() {
    std::vector<var_t> vars = {1, 2, "Hello, World!"};

    for (auto& v : vars) {
        std::visit(
            MakePrint( // (2)
                [](const char* str) { puts(str); },
                [](int i) { printf("%d\n", i); }
                ),
            v);
    }

    return 0;
}

这已经和overload非常接近了,回到一开始我们提到的,如何写成开头那个样子呢,这就需要c++

17 的新特性,类模板实参推导, 自定义推导指引,User-defined deduction guides,简单说,就是构造函数能做helper的活(make_tuple, make_pair),只要定义好规则就可以

在c++17中,可以干净的写出

std::tuple t(4, 3, 2.5); // same as auto t = std::make_tuple(4, 3, 2.5);

在tuple中,写好了推导规则

#ifndef _LIBCPP_HAS_NO_DEDUCTION_GUIDES
// NOTE: These are not yet standardized, but are required to simulate the
// implicit deduction guide that should be generated had libc++ declared the
// tuple-like constructors "correctly"
template <class _Alloc, class ..._Args>
tuple(allocator_arg_t, const _Alloc&, tuple<_Args...> const&) -> tuple<_Args...>;
template <class _Alloc, class ..._Args>
tuple(allocator_arg_t, const _Alloc&, tuple<_Args...>&&) -> tuple<_Args...>;
#endif

make_tuple就下岗了

类似的,只要为Print写好推导,就可以省掉MakePrint

#include <variant>
#include <cstdio>
#include <vector>

using var_t = std::variant<int, const char*>;

template <class... Ts>
struct Print : Ts... {
    using Ts::operator()...;
};

template <class...Ts> Print(Ts...) -> Print<Ts...>; // (1)

int main() {
    std::vector<var_t> vars = {1, 2, "Hello, World!"};
    for (auto& v : vars) {
        std::visit(
            Print{ // (2)
                [](const char* str) { puts(str); },
                [](int i) { printf("%d\n", i); }
            },
            v);
    }
    return 0;
}

到此,overloaded trick就解释完了

reference

看到这里或许你有建议或者疑问或者指出我的错误,请留言评论或者邮件mailto:wanghenshui@qq.com, 多谢!

觉得写的不错可以点开扫码赞助几毛 微信转账
Read More

std::variant 与 std::visit

why

因为rust的enum让我回想起union和 variant,决定找找文档仔细说一下这个variant,做个笔记

std::variant是c++17加入的新容器,主要就是safe union。用来和enum比较也算合适,都叫做sum type,类型是线程(求和)的,只表现出线性数目的类别实例,product type是乘积的(比如结构体),这个是函数式概念了,先做个科普

下面是一个std::visit+ std::variant的例子,同比rust中的enum match

std::variant<double, bool, std::string> var;

struct {
    void operator()(int) { std::cout << "int!\n"; }
    void operator()(std::string const&) { std::cout << "string!\n"; }
} visitor;

std::visit(visitor, var);
#![allow(unused_variables)]
fn main() {
enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}
}

差距还好。rust也可以直接调用函数 lambda。对比来说,c++需要手动写visitor有点难看。有没有make_visitor呢

overload

下面的链接有make_visitor, 就是这个overload,在cpp reference std::visit的示例中,也有使用overload这个模板,长这个样子

template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;

于是,上面的代码就变成这样

std::variant<double, bool, std::string> var;
std::visit(overloaded {
            [](auto arg) { std::cout << arg << ' '; },
            [](double arg) { std::cout << std::fixed << arg << ' '; },
            [](const std::string& arg) { std::cout << std::quoted(arg) << ' '; },
        }, var);

感觉稍微干净了点是不是?手写operator()还是有点难受的?换成lambda只能写一个,也得用overload包装一下

overload原理就是模板推导和转发,变参模板可能看不懂,写成一个继承的就容易明白了

struct overloadInt{ 
    void operator(int arg){
        std::cout<<arg<<' ';
    } 
};
struct overload : overloadInt{
    using overloadInt::operator();
};

不用std::visit行不行

行,又要走SFINAE 老路了,enable_if 糊一个,还要判断variant里到底存了什么,基本上和visit差不多?我糊了半天糊出个这么个玩意儿。

#include <iomanip>
#include <iostream>
#include <string>
#include <type_traits>
#include <variant>
#include <vector>

template<typename T, typename VARIANT_T>
struct is_variant_member_type;

template<typename T, typename... Ts>
struct is_variant_member_type<T, std::variant<Ts...>> 
  : public std::disjunction<std::is_same<T, Ts>...> {};


template <typename V > typename std::enable_if<is_variant_member_type<std::string,V>::value&&
    is_variant_member_type<double,V>::value>&&
    is_variant_member_type<int,V>::value>::type
match (V v)
{
    if  (std::holds_alternative<int>(v))
    	std::cout << std::get<int>(v) << ' '; 
    if (std::holds_alternative<std::string>(v))
        std::cout << std::quoted(std::get<std::string>(v)) << ' ';
    if  (std::holds_alternative<double>(v))
        std::cout<<std::fixed << std::get<double>(v) << ' ';
}

// the variant to visit
using var_t = std::variant<int, double, std::string>;

int main() {
    std::vector<var_t> vec = {10, 15l, 1.5, "hello"};
    for(auto& v: vec) {
        match(v);
    }
}

注意,用if-constexpr不可以,虽然std::holds_alternative是constexpr的。。暂时没搞懂

感觉吧match拆一拆,拆成lambda类似形式的,可以结合overload。这个写的用不用enable_if没什么区别。。我以后再写吧。。这里学的不明白。

reference

  • std::variant https://en.cppreference.com/w/cpp/utility/variant
  • std::visit , 其中这个overlord模板很有意思。https://en.cppreference.com/w/cpp/utility/variant/visit
  • 一个variant介绍,其中里面的 make_visitor就是上面这个overloadedhttps://pabloariasal.github.io/2018/06/26/std-variant/
  • 对overload的解释 https://dev.to/tmr232/that-overloaded-trick-overloading-lambdas-in-c17
  • 对overload的解释和加强,并且有提案。https://arne-mertz.de/2018/05/overload-build-a-variant-visitor-on-the-fly/
  • std::visit 和std::variant https://arne-mertz.de/2018/05/modern-c-features-stdvariant-and-stdvisit/
  • 讲type的,深入浅出(应该写个笔记记录下)https://github.com/CppCon/CppCon2016/blob/master/Tutorials/Using%20Types%20Effectively/Using%20Types%20Effectively%20-%20Ben%20Deane%20-%20CppCon%202016.pdf
  • rust enum+match https://doc.rust-lang.org/beta/book/ch06-02-match.html
  • 观点:std::visit很糟糕 https://bitbashing.io/std-visit.html
  • visit 实现,里面有几个链接很有意思,https://stackoverflow.com/questions/47956335/how-does-stdvisit-work-with-stdvariant
    • https://mpark.github.io/programming/2015/07/07/variant-visitation/
    • https://mpark.github.io/programming/2019/01/22/variant-visitation-v2/
    • http://talesofcpp.fusionfenix.com/post-17/eggs.variant—part-i
    • 上面的链接有两个variant的实现。

看到这里或许你有建议或者疑问或者指出我的错误,请留言评论或者邮件mailto:wanghenshui@qq.com, 多谢!

觉得写的不错可以点开扫码赞助几毛 微信转账
Read More


一些c api 拾遗

Why

记录一些零碎的c的东西

bzero bcopy vs memset memcpy

https://stackoverflow.com/questions/18330673/bzero-bcopy-versus-memset-memcpy

简而言之 bzero相当于memset bcopy相当于mommove

// void bzero(void *s, size_t n);
#define bzero(s, n) memset((s), 0, (n))

// void bcopy(const void *s1, void *s2, size_t n);
#define bcopy(s1, s2, n) memmove((s2), (s1), (n))
memmove vs momcpy

https://stackoverflow.com/questions/1201319/what-is-the-difference-between-memmove-and-memcpy

区别在于src和dst可不可以重叠

strcasecmp

一个常见的比较字符串的需求,不分大小写

在linux上可以用strcasecmp,在windows上可以用stricmp,需要写个宏糊到一起,当然,也有其他办法,参考链接给出了很多种实现

比如下面这个不怎么费力的

#include <algorithm>
bool iequals(const string& a, const string& b)
{
    return std::equal(a.begin(), a.end(),
                      b.begin(), b.end(),
                      [](char a, char b) {
                          return tolower(a) == tolower(b);
                      });
}

或者boost::iequals,这个是怎么实现的?

        //! 'Equals' predicate ( case insensitive )
        /*!
            This predicate holds when the test container is equal to the
            input container i.e. all elements in both containers are same.
            Elements are compared case insensitively.

            \param Input An input sequence
            \param Test A test sequence
            \param Loc A locale used for case insensitive comparison
            \return The result of the test

            \note This is a two-way version of \c std::equal algorithm

            \note This function provides the strong exception-safety guarantee
        */
        template<typename Range1T, typename Range2T>
        inline bool iequals( 
            const Range1T& Input, 
            const Range2T& Test,
            const std::locale& Loc=std::locale())
        {
            return ::boost::algorithm::equals(Input, Test, is_iequal(Loc));
        }

#define offsetof(a,b) ((int)(&(((a*)(0))->b)))
define offsetof(struct_t,member) ((size_t)(char *)&((struct_t *)0)->member)

(struct_t *)0是一个指向struct_t类型的指针,其指针值为 0,所以其作用就是把从地址 0 开始的存储空间映射为一个 struct_t 类型的对象。((struct_t *)0)->member 是访问类型中的成员 member,相应地 &((struct_t *)0)->member) 就是返回这个成员的地址。由于对象的起始地址为 0,所以成员的地址其实就是相对于对象首地址的成员的偏移地址。然后在通过类型转换,转换为 size_t 类型(size_t一般是无符号整数)。

所以,offsetoff(struct_t,member)宏的作用就是获得成员member在类型struct_t中的偏移量。

对实时嵌入式系统,MISRA–作为工业标准的C编程规范的Rule 120 禁止使用offsetof.

Container_of在Linux内核中是一个常用的宏,用于从包含在某个结构中的指针获得结构本身的指针,通俗地讲就是通过结构体变量中某个成员的首地址进而获得整个结构体变量的首地址。

Container_of的定义如下:

#define container_of(ptr, type, member) ({      \  
    const typeof( ((type *)0)->member ) *__mptr = (ptr);    \  
    (type *)( (char *)__mptr - offsetof(type,member) );})  

其实它的语法很简单,只是一些指针的灵活应用,它分两步:

第一步,首先定义一个临时的数据类型(通过typeof( ((type *)0)->member )获得)与ptr相同的指针变量__mptr,然后用它来保存ptr的值。

第二步,用(char *)__mptr减去member在结构体中的偏移量,得到的值就是整个结构体变量的首地址(整个宏的返回值就是这个首地址)。

简单汇编

push src 等同于esp <- esp -4, [esp] <-src更新栈指针,保存值

pop dst 等同于 dst <- [esp], esp+4; 取出值,更新栈指针

move dst src 好懂,可能at&t的汇编风格和Intel的汇编风格不一致

leave 等同于move esp, ebp ;pop ebp

call src 相当于push eip eip <- src 保存旧的eip, 把src赋给eip更新成新的作用域

ret就相当于pop eip

位域

struct tcphdr
  {
    u_int16_t th_sport;         /* source port */
    u_int16_t th_dport;         /* destination port */
    tcp_seq th_seq;             /* sequence number */
    tcp_seq th_ack;             /* acknowledgement number */
#  if __BYTE_ORDER == __LITTLE_ENDIAN
    u_int8_t th_x2:4;           /* (unused) */
    u_int8_t th_off:4;          /* data offset */
#  endif
#  if __BYTE_ORDER == __BIG_ENDIAN
    u_int8_t th_off:4;          /* data offset */
    u_int8_t th_x2:4;           /* (unused) */
#  endif
    u_int8_t th_flags;
#  define TH_FIN        0x01
#  define TH_SYN        0x02
#  define TH_RST        0x04
#  define TH_PUSH       0x08
#  define TH_ACK        0x10
#  define TH_URG        0x20
    u_int16_t th_win;           /* window */
    u_int16_t th_sum;           /* checksum */
    u_int16_t th_urp;           /* urgent pointer */
};

localtime不可重入 https://stackoverflow.com/questions/35806261/how-to-use-mktime-without-changing-the-original-tm-struct

如果对同一个结构体指针调用两次,会返回同一个结果,不管你的指针如何改动,使用localtime_r 或者别用这个傻逼函数

c23 localtime_r进了标准,各个平台都会实现,没有兼容性问题

ref


看到这里或许你有建议或者疑问或者指出我的错误,请留言评论或者邮件mailto:wanghenshui@qq.com, 多谢!

觉得写的不错可以点开扫码赞助几毛 微信转账
Read More

软件调试的艺术 读书笔记



预备知识

  • gdbinit启动文件

  • gdb -command=z x 在x上运行gdb,从z文件中读命令

  • 原理 ptrace

停下来看一看

  • 断点
    • 创建
      • break function
      • break line_number
      • break filename:line_number 注意相对路径
      • break filename:line_number
      • break filename:function
    • 删除 delete clear
    • 禁用disable
    • 继续执行
      • 下一行,next 执行函数不会暂停 step进入函数停在第一行
      • continue c继续执行到下一个断点,continue n 跳过n个断点
      • finish fin当前函数执行结束
      • util u 快速执行完循环
        • 可能违反直觉,但是实际上是跳到比当前地址更高的位置上去
        • 看汇编直观一些
        • todo:需要分析一波
    • 属性
      • info breakpoints
  • 条件断点
    • break break-args if (condition)
      • 注意优先级
      • 注意表达式的返回值默认是int,需要定义指针强转函数符号
  • 断点命令列表
    • commands 1 xx end
  • 监视点
    • watch
  • 捕获点

其实早期gdb对于断点没有明确区分。delete可以同时删除这三种点,注意

检查和设置变量

  • print p p可以打印字段,也可以打印结构体
  • display disp 每次断点击中都会打印上次display的内容, p升级版
  • commands组合
    • commands + call
    • commands组合命令也可以录入命令来做复现
  • 数组
    • p *x@25 *pointer@number_of_elements
    • 支持强制类型转换 p (int [25]) *x
  • c++
    • 需要带上命名空间/类空间
    • ptype打印结构
  • 局部变量 info locals
  • x 查看内存地址 x/10wx addr
    • x/10i addr
  • p disp 高进选项
    • p/x y 十六进制查看
    • dis disp 1临时禁止display 1
  • gdb 变量
    • 值历史 p *$1 p *$也可以
    • convenience variable set $q = p

程序崩溃处理

  • 崩溃相关的基本内存布局以及页的概念
    • 页表,页表项,以及为什么会有段错误 -> 数据段 栈区,堆区的权限校验
      • 轻微的内存访问程序错误可能不会导致段错误,比如越界
      • 段错误也可以捕获,故意触发段错误来执行一些动作(todo想不到这么做为了啥)
    • 其他信号可能造成的崩溃,比如fpe ,buserr
  • core文件
    • 注意某些shell禁止coredump ,ulimit -c unlimited
  • 建议,调试重编期间不要退出gdb。多shell管理

多活动上下文中的调试

  • 网络编程 gdb 系统函数+ strace系统函数
  • 信号 handle
  • 多线程,切换线程看函数调用栈
    • info threads thread n bt f n
    • break cond thread n(当线程3到达源代码行时停止执行)
    • thread apply bt all
      • 打印过长导致进程复位,或者连接断开,使用不阻塞方式
        • 查线程id find / proc/pid/task -name "sched" |xargs grep threads
        • pstack
    • 转到线程t
    • set scheduler-locking off|on|step控制当前线程调度
  • 多进程MPI
    • attach到进程上看调用栈以及对应栈帧上的信息
  • 不阻塞 gdb -batch -ex “cmd” -p pid

特殊主题

不说了。编译连接错误之类的

其他工具

  • 充分利用编译警告 ide等等工具
  • 检查系统错误码之类的
  • strace ltrace
  • lint
  • 内存问题,MALLOC_宏(valgrind没介绍)

其他语言调试

基本大同小异

最好要了解一点汇编

没有符号信息,自己编译一套-g的o文件,反汇编 有-g obj -d -S -l xx.o, 没-gobjdump -dr xx.o

如果没有o文件,反汇编看call调用自己推到调用栈

​ 找特殊调用 寻址偏移 特殊值


ref

  • https://github.com/hellogcc/100-gdb-tips/

看到这里或许你有建议或者疑问或者指出我的错误,请留言评论或者邮件mailto:wanghenshui@qq.com, 多谢!

觉得写的不错可以点开扫码赞助几毛 微信转账
Read More

^