c++编程思想 读书笔记
##
##
讲了几种pointer以及背后的惯用法
class C;
class SBRM{
C* pc;
public:
SBRM(C* c):pc(c){}
~SBRM(){delete pc;}
operator C*(){return pc;}
};
...
for(int i=0; i<100; ++i){
SBRM cc(new C(i)) ;
C* c= cc;
/*do sth about c*/
}
//没有SBRM管理是这样的
for(int i=0; i<100; ++i){
C* c(new C(i)) ;
/*do sth about c*/
delete c;
}
能做什么
scoped_ptr vs unique_ptr
scoped_ptr是c++11前产物,没有定制deleter和转移所有权(move), 针对a数组有scoped_array unique_ptr没有分开,功能合到一起了
shared_ptr
RAII,引用计数,可复制,定制deleter, make_shared比new要快(省一次new,避免异常问题)
一个实现结构
感知shareshare-aware
enable_shared_from_this
用this构造shared_ptr是十分危险的,如果这个shared_ptr 多次拷贝,就会有double-free问题
这个问题本质上和多个shared_ptr由同一个裸指针初始化
是同一个场景
struct Bad
{
std::shared_ptr<Bad> getptr() {
return std::shared_ptr<Bad>(this);
}
~Bad() { std::cout << "Bad::~Bad() called\n"; }
};
{
// Bad, each shared_ptr thinks it's the only owner of the object
std::shared_ptr<Bad> bp1 = std::make_shared<Bad>();
std::shared_ptr<Bad> bp2 = bp1->getptr();
std::cout << "bp2.use_count() = " << bp2.use_count() << '\n';
}// UB: double-delete of Bad
struct Good: std::enable_shared_from_this<Good> // note: public inheritance
{
std::shared_ptr<Good> getptr() {
return shared_from_this();
}
};
{
// Good: the two shared_ptr's share the same object
std::shared_ptr<Good> gp1 = std::make_shared<Good>();
std::shared_ptr<Good> gp2 = gp1->getptr();
std::cout << "gp2.use_count() = " << gp2.use_count() << '\n';
}
这个例子也展示出,本质上需要把this提升成shared_ptr,
不放在本体中,就只能放在enable_shared_from_this里,通过继承来侵入,然后调用share_from_this来提升(weak_ptr,shared_ptr有可能循环)
shared_ptr构造中判断是否是enable_shared_from_this基类( 函数重载)来做钩子,构造weak_ptr
引用计数指针的两种实现 侵入式,非侵入式
对象级别引用计数,侵入式 boost::intrusive_ptr
(感觉这个早晚也得进标准库) COM (这个应该没人用吧) 方便管理和转换普通指针,但是对象结构复杂
容器级别,shared_ptr<T>
干净,就是不能和普通指针混着用,需要转来转去
todo
为什么使用并发
std::thread
转移线程所有权 (本身可以移动,不可以拷贝)
class scoped_thread{
std::thread t_;
public:
explicit scoped_thread(std::thread t)t_(std::move(t)){
if (!t_.joinable())
throw std::logic_error("No thread");
}
~scoped_thread(){
t.join();
}
scoped_thread(scoped_thread const&) = delete;
scoped_thread& operator=(scoped_thread const&) = delete;
};
c++17建议std::jthread
自带上面的析构功能,不用手写join 标准库里已经有了
一行循环join,确实比range-for好看点
std::for_each(threads,begin(), threads.end(), std::mem_fn(&std::thread::join));
std::thread::hardware_concurrency()
使用线程数暗示
` std::thread::get_id
std::this_thread::get_id()`
共享数据带来的问题(多用assert)
###
###
先谢指教。
场地在sub-live, 场地有点破。高德地图上没有。走到一个破技校门口,下面有个小黑板写着往里走sub-live。我找了半天。路遇一个大哥看我手机上的票二维码,问我是不是也是看乐队演出的。我说是,他说这地儿也太难找了。退票妈的。
然后就陆续进场,鉴于自己的身高我就没有硬往前挤,站在第三排就能看见主唱。后面倒是来了几个女生,让她们往前面钻。
刚开场那会大概有五六十人的样子,后续能有百十人。主场说人还可以,不多也不少,不至于尴尬。
然后吐槽了哈尔滨的春天,来了首残酷春日。说实话日式摇滚我还真是不太喜欢。比较直接的几首歌感觉还行。前面的十年所见,北海,这几首老歌都很棒。然后唱了丙申歌合战和京城武斗会的大部分歌曲(除了永不再亮的头像)。唱了玻璃杯+天赐的毒药。这段太牛逼了。我是特别喜欢。然后是后面《永动机》碟的内容+我知道那天晚上你又没睡着。我嗓子都喊哑了。
我之前看主唱列的巡演歌单中没有这么多。应该算是返场吧。多唱了很多,唱到九点十分然后卖纪念册和cd,和衣服(衣服三百,鉴于我自己的购买力就放弃了)
中间主唱也说了点段子。说来哈尔滨也不是“重新认识一下”(本次巡演的主题)而是第一次见。说这些嗑也就给现场的朋友了,音乐电台也不做了。感谢现场的观众养活这个乐队。还说自己牙口不好,这个话题跳的太快。没法接。一度尬笑。
主唱有点否认自己。怕这波歌迷喜欢之前永动机那种“古风”“中国风”之类的刻板印象(应该指的是贝贝,天地一家春这些)。着急的想摆脱这个形象。其实《皇城饮恨录》《战歌》这两张碟的还是很牛逼的,也不是说喜欢这个就不能接收日式摇滚这种。主唱老是否定自己让我很同情。我也是每年都在否定自己觉得自己傻逼。觉得自己过去也没啥闪光点。过去一直存在。否定没有意思。
售后签只买了纪念册(cd用不到衣服也用不到)。纪念册买了也有点后悔,封面说内容十分那啥。我还在猜毁三观的内容能是啥,结果打开看是主唱女装。我日。被人翻到我不就gay了。让主唱签名,忘记让他多签俩字儿了。当时有点紧张。
年轻人可真他妈能蹦啊。我在后面摇头就很累了。这帮年轻人又是蹦又是跳又是摆手的。我旁边有个哥们摇摇晃晃带个耳机像个女生,一说话又是个小男生的动静。让旁边摇头的我十分恍惚。
以上就是演唱会。我看了演唱会返图,还挺好看的。场地的人说其他地方的livehouse也一样破。我没有见识过就不下定论了。
这个乐队后面解散了。我不再去livehouse了。给年轻的乐队一些机会
这个讲的是一些小组件,部分在AA的书中介绍过
Functor , RAII, Concepts 这些老生常谈不说了
Policy Class
作者列举了AA书中的NullPointer的例子,其实这个Policy Class更像type traits中的tag dispatch手法。或者说,Concept约束。没啥好讲的
CRTP
静多态
写了个cloneable
template <typename Derived>
struct cloneable{
Derived* clone() const{
return new Derived(static_cast<Derived const&>(*this));
}
};
struct bar : clonealbe<bar>{...};
还有一个经典的例子是enable_shared_from_this, 作为一个观测者(weak_ptr),需要shared的时候抛出去shared_ptr
template <class T>
class enable_shared_from_this{
mutable weak_ptr<T> weak_this_;
public:
shared_ptr<T> shared_from_this(){
shared_ptr<T> p(weak_this_);
return p;
}
...
};
总之这是公共接口静多态实现的一个方法,可以把子类的this拿过来霍霍,比如
template<class Derived>
struct enable_profit{
float profit(){
Derived const & d = static_cast<Derived const &>(*this);
return 42*(d.output)/d.input;
}
};
struct department : enable_profit<department>}{
int output;
int input;
};
Type Traits 没啥好讲的
Tag Dispatching or SFINAE
这个典型就是enable_if
看完感觉就是在复习。
这个ppt讲的是std::thread, 算是一个教学指南
构造函数声明是这个样子的
struct thread{
template<class F, class ...Args> explicit
thread(F&&f, Args&&... args);
};
一例
#include <thread>
std::map<std::string, std::string> french
{ {"hello","bonjour"},{"world","tout le monde"} };
int main(){
std::string greet = french["hello"];
std::thread t([&]{std::cout<<greet<<", ";});
std::string audience = freanch["world"];
t.join();
std::cout<< audience<<std::endl;
}
这是普通用法,如果想传参数引用,而不是捕获怎么办 ->转成ref,std::ref
std::thread t([](const std::string& s){std::cout<<s<<", ";}, std::ref(greet));
` std::thread overreview`
不可复制 ->移动语义
系统相关的细节不涉及(调度,优先级)
joinability
几个条件
所以这就有几个典型异常场景
就上面的例子,第八行如果挂掉,线程就忘记join了。这样thread析构会直接调用std::terminate,所以需要catch住,在catch里join一下,然后把join转发出去
try {
std::string audience = french["world"];
} catch(...) {
t.join;
throw;
}
可真难看啊。
this_thread
接口是这样的
namespace this_thread{
thread::id get_id() noexcept;
void yield() noexcept;
template <class Clock class Duration>
void sleep_until(
const chrono::time_point<Clock, Duration>& abs_time);
template <class Rep, class Period>
void sleep_for(
const chrono::duration<Rep,Period>& rel_time);
}
太长了不好记
后面两个可以记成
void sleep_until(time_point);
void sleep_for(duration);
std::async std::future
上面例子的写法很难看,作者使用async 和future重写了一下
#include <future>
...
std::fucure<void> f = std:;async([&]{std::cout<< greet<<", ";});
std::string audience = french["world"];
f.get();
...
Or…
...
auto greet = std::async([]{return french["hellp"];});
std::string audience = french["world"];
std::cout<<greet.get()<<", "<<audience<<std::endl;
这样好看多了
async的launch逻辑
async | deferred |
一个例子
template <class Iter>
void parallel_merge_sort(Iter start, Itera finish) {
std::size_t d = std::distance(start, finish);
if(d<=1) return;
Iter mid = start; std::advance(mid, d/2);
auto f = std::async(
d<768?std::launch::deferred
: std::launch:deferred | std::launch::async,
[=]{parallel_merge_sort(start,mid);});
parallel_merge_sort(mid, finish);
f.get();
std::inplace_merge(start,mid,finish);
}
std::future
api是这个样子
enum class future_status{ready, timeout, deferred};
template <class R> struct future {
future() noexcept;
bool valid() const noexcept;// ready
R get();
void wait() const;// wait for ready
future_status wait_for(duration) const;
future_status wait_untile(time_point) const;
shared_future<R> share();
};
整体是move-only的,有个share接口用于共享
std::shared_future
接口和future差不多,提供copy干脏活的
enum class future_status{ready, timeout, deferred};
template <class R> struct shared_future {
future() noexcept;
bool valid() const noexcept;// ready
R get();
void wait() const;// wait for ready
future_status wait_for(duration) const;
future_status wait_untile(time_point) const;
shared_future(future<R>&& f) noexcept;
};
std::promise
如果用promise来重构,能彻底异步解耦,代码更好看一些
int main() {
std::promise<std::string> audience_send;
auto greet = std::async(
[](std::future<std::string> audience_rcv)
{
std::cout<<french["hello"] <<", ";
std::cout<<audience_rcv.get()<<std:;endl;
},
audience_send.get_future()//pull
);
audience_send_value(french["world"]);//push
greet.wait();
}
std::promise api
template <class R>
struct promise{
promise();
template <class Allocator>
promise(allocator_arg_t, const Allocator& a);
future<R> get_future(); //pull the future
void set_value(R); //push, make the future ready
void set_exception(exception_ptr p);
void set_value_at_thread_exit(R); //push result but defer readiness
void set_exception_at_thread_exit(exception_ptr p);
};
推迟异步动作,deferring launch, -> std::packaged_task
更进一步,把 std::async 换成std::packaged_task, 必须显式调用operator()才会执行,不像async立即异步执行
int main(){
std::packaged_task<std::sring()> do_lookup(
[]{return french["hello"];});
auto greet = do_lookup.get_future();
do_lookup();
std::string audience = french["world"];
std::cout<<greet.get()<<", "<<audience<<std::endl;
}
std::packaged_task长这个样子
template<class> class packaged_task;// undefined
template<class R, class... ArgTypes>
struct packaged_task<R(ArgTypes...)> {
packaged_task() noexcept;
template <class F> explicit packaged_task(F&& f);
template <class F, class Alloc>
explicit packaged_task(allocator_arg_t, const Alloc& a, F&& f);
future<R> get_future();//pull
bool valid() const noexcept;
void operator()(ArgTypes...);//make the future ready(push)
void make_ready_at_thread_exit(ArgTypes...);
void reset();
};
基于锁的数据共享
基本概念,线程安全,强线程安全
std::mutex
实现一个强线程安全的堆栈
template <class T>
struct shared_stack{
bool empty() const {
std::lock_guard<std::mutex> l(m);
bool r = v.empty();
return r;
}
T top() const{
std::lock_guard<std::mutex> l(m);
T r = v.back();
return r;
}
void pop();
void push(T x);
private:
mutable std::mutex m;
std::vector<T> v;
};
这里讨论了锁,lock_guard和unique_lock
实现一个线程安全的队列
template<unsigned size, class T>
struct bounded_msg_queue{
bounded_msg_queue()
:begin(0),end(0),buffered(0){}
void send(T x){
{
std::unique_lock<std::mutex> lk(broker);
while(buffered == size)
not_full.wait(lk);
buf[end] = x;
end = (end +1)%size;
++buffered;
}
not_empty.notify_all();
}
T receive(){
T r;
{
std::unique_lock<std::mutex> lk(broker);
while (buffered == 0)
not_empty.wait(lk);
r = buf[begin];
begin = (begin+1) % size;
-- buffered;
}
not_full.notify_all();
return r;
}
private:
std::mutex broker;
unsigned int begin, end, buffered;
T buf[size];
std::condition_variable not_full, not_empty;
};
std::condition_variable
这个api和pthread原语差不多,不说了
boost::shared_mutex
这个可以用于多读少写的场景,有点读写锁封装的感觉。具体没有研究
IO线程解包,worker线程干活,当灌入数据,IO线程跑不满CPU,是什么原因
延迟和压力有关,如何才能保证最大的ops和最好的延迟呢,怎么调服务呢?
每五分钟就想摸一次手机
查cpu sar -u 1
查流量 dstat
sar -n DEV 1
relocatable executable shared object
格式都一样,布局也一样,代码段数据段
__attribute__((section("FOO")))
elf结构
ELF头 |
---|
.text |
.data |
.bss |
其他段 |
section header table,段表 readelf查看 elf 头串起段表 |
string table symbol table… |
特殊符号__executable_start
__etext
__edata
符号,name mangleing,extern “C”
强符号,弱符号,默认强符号 __attribute__((week))
强引用,弱引用,默认强引用 __attribute__((weakref))
void foo 用来被覆盖
弱符号典型代表,未初始化的全局变量
相似段合并
空间地址分配
符号解析重定位
__attribute__((nocommon))
)c++相关的链接问题
菜谱与炒菜
overlay vs paging
paging 页映射
创建独立虚拟地址空间
执行文件头,建立虚拟空间与可执行文件的映射,准备照着菜谱炒菜
可执行二进制文件又叫image懂了伐
VMA
将CPU指令集设定成可执行文件入口地址,启动执行
页错误
ELF文件链接视图和执行视图
堆和栈也是VMA cat /proc/pid/maps
类似堆和栈,vdso 内核交互vma
总结四中VMA类型
读 | 写 | 执行 | 映像文件 | |
---|---|---|---|---|
代码 VMA | √ | x | √ | √ |
数据VMA | √ | √ | √ | √ |
堆VMA | √ | √ | √ | 匿名,无映像,可向上扩展 |
栈 | √ | √ | x | 匿名,无映像,可向下拓展 |
内核装载ELF的优化
段地址对齐以及优化
静态链接磁盘一份内存一份造成的浪费
动态链接程序运行时地址分布
代码段多出来libc ld和动态库
装载时重定位以及地址无关代码PIC
装载时重定位和链接时重定位差不多,没有重复利用代码。引入地址无关PIC可以重复使用,即尽量让地址相关的代码放到数据段
代码段复用,数据段各自复制
模块内部调用,相对地址调用,无需重定位
模块内部数据访问,拿到PC (内部hack)+ 记录的偏移量
call 484 <__i686.get_pc_thunk.cx>
add $0x118c, %ecx
movl $0x1, 0x28(%ecx)
模块间数据访问,数据段中建立全局偏移表。间接引用
模块间调用 也是全局偏移表,保存目标函数地址 存在性能问题。elf有优化
call 484 <__i686.get_pc_thunk.cx>
add $0x118c, %ecx
mov 0xfffffffc(%ecx), %eax
call *(%eax)
全局变量怎么处理
数据段地址无关性
延迟绑定PLT
调用时再绑定(这种理念到处都有啊原来)
_dl_runtime_rosolve()
bar@plt:
jmp *(bar@GOT)
push n
push moduleID
jump _dl_runtime_resolve
从.got
段里拆出来。.got.plt
段
.dynamic
地址_dl_runtime_resolve
地址动态链接相关结构
.interp
段,专门记录ld目录,字符串.dynamic
导出符号表 .hash
加速查找.rel.dyn
.rel.plt
.dynamic
是入口点ET_EXEC
还是ET_DYN
,就是装载然后转移给ELF入口
e_entry
, .interp
rundll32.exe
可以吧dll强行按照可执行文件执行_dl_start -> boostrap -> _dl_start_final -> _dl_sysdep_start -> _dl_main _dl_main
本身来判断自己是ld还是其他显示运行时链接
dlopen, dlsym dlerror dlclose
dlopen
LD_LIBRARY_PATH
/etc/ld.so.cache
/lib
/usr/lib
.init
dlsym
根据dlopen返回的handle来查符号libname.so.x.y.z
libc.so.2.6.1 -> libc.so.6
ld.so.2.6.1 ->ld-linux.so
GLIBC_2.6.1
,更新符号来保证依赖/lib
系统关键库(动态链接器,c运行时,数学库,bin
sbin
用到的库)/usr/lib
非系统运行时的关键共享库,静态库,目标文件。不会被用户用到/usr/local/lib
第三方库,python解析器的lib,之类的.dynamic
段中DT_NEED
列出路径,如果是绝对路径,就会找这个文件,如果是相对路径,就会从/lib
/usr/lib
/etc/ld.so.conf
配置文件指定的目录中查找
/etc/ld.so.conf
中的目录必然很慢,ldconfig会cache一份/etc/ld.so.cache
ldconfig
重新cache一份LD_LIBRARY_PATH
临时更改某个应用程序的共享库查找路径,不影响整体
LD_LIBRARY_PATH=/home/user /bin/ls
/lib/ld-linux.so.2 -library-path /home/user /bin/ls
LD_LIBRARY_PATH
-> /etc/ld.so.cache
-> /usr/lib
, /lib
LD_LIBRARY_PATH
,最好不要export
LD_PRELOAD
指定覆盖,优先加载,比LD_LIBRARY_PATH
优先级还高
/etc/ld.so.preload
LD_DEBUG
打开动态链接器的调试功能
gcc -shared -Wl, -soname, my_soname -o libraty_name source_files libraty_files
-fomit-frame-pointer
LD_LIBRARY_PATH
,也可以-rpath=/home/mylib
dlopen
可能就会反向引用失败,使用-export-dynamic
__attribute__((destructor))
在main执行结束/dlclose返回之前执行程序的内存布局
栈与调用惯例
堆栈帧
函数返回地址和参数
临时变量
保存的上下文 寄存器 ebp esp
push ebp#后面会出栈恢复
mov ebp, esp
#sub esp, xxx
#push xxx
####结束后,与开头正好相反
#pop xxx
mov esp, ebp
pop ebp
ret
调用惯例
cdecl
函数返回值传递
rep move
或者call memcpy
堆与内存管理
main并不是开始
void _start()
{
%ebp = 0;
int argc = `pop from stack`;
char** argv = `top of stack`;
__libc_start_main(main, argc, argv, libc_csu_init, __lib_csu_fini, edx, `top of stack`);
}
exit都做了什么
遍历函数链表,执行atexit __cxa_atexit
movl 4(%esp), %ebx
movl $__NR_exit, %eax ;call exit
int $0x80; halt如果exit退出失败,就强制停止。一般走不到这里
运行库与IO
C/C++运行库
glibc
运行库和多线程
c++全局构造与析构
__libc_csu_init
-> _init()
调用init段 -> __do_global_ctors_aux
crtbegin.o
由gcc/Crtstuff.c
编好。内部会有__CTOR_LIST__
如何生成?- > 所有的.ctor
段拼凑-> crtbegin.o
串起来
crtend.o
负责定义__CTOR_END__
指向.ctor
末尾IO 初探,通过fread
linux-gate.so.1
aka [vdso]
可以通过maps查看,占用4k,可以导出内部细节就是sysenter等把印象笔记收藏的链接捞出来
https://just-taking-a-ride.com/inside_python_dict/chapter1.html
https://medium.com/@ehudt/redis-hash-table-scan-explained-537cc8bb9f52
https://www.lysator.liu.se/c/pikestyle.html
http://git.kernel.dk/cgit/linux-block/commit/?h=aio-poll&id=5aeaa1ad235c708e31ad930d1ff6ba6fd39bee91
https://blog.conjur.org/special-cases-are-a-code-smell/
https://www.outlyer.com/blog/why-not-to-build-a-time-series-database/
https://99designs.com/blog/engineering/gqlgen-a-graphql-server-generator-for-go/
https://www.haiku-os.org/docs/HIG/index.xml
https://www.scientificamerican.com/article/algorithms-designed-to-fight-poverty-can-actually-make-it-worse/
这个考虑优先翻译
https://arjunsreedharan.org/post/148675821737/memory-allocators-101-write-a-simple-memory#=
https://blog.gopheracademy.com/advent-2018/avoid-gc-overhead-large-heaps/