如下两个结构体 A B ,他们的实例的大小相同吗?
|
struct A{ char c_1; int i; char c2; }; struct B{ char c_1; char c_2; int i; }; |
两个 char
共占两个字节, 一个 int
占4个字节, 所以两个结构体都是 6个字节。但事实并非如此,在大部分计算机中,他们都不会只占 6 个字节。这涉及到 内存对齐
需知,计算机访问内存的方式,并不是一个字节一个字节访问的,而是以字长(word size)
为单位来进行访问的。32位计算机的字长为 4 字节, 64位计算机的字节为 8 字节。而所谓内存对齐, 就是将变量调整至字长的倍数的位置存放,以方便计算机访问
。
上面两个结构体在内存中的布局最有可能是这个样子的:(这里说 最有可能 是因为具体的布局要视计算机而定)
我们使用一段代码来验证:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
void showmem(unsigned char* p, uint32_t sz){ int len = sizeof(int*); cout<<"\t"; for(int i = 0; i<len; i++){ cout<<i<<"\t"; } cout<<endl; for(int i = 0; i<=len; i++){ cout<<"-------|"; } cout<<endl; unsigned char* t = p; uint32_t i = 0; char msg[8]; while(i < sz){ sprintf(msg, "%02d", i); cout<<msg<<"|\t"; for(int j = 0; j< len && (i+j)<sz; j++){ sprintf(msg, "%04d", *(uint8_t*)(t+j)); cout<<msg<<"\t"; } cout<<endl; i += len; t += len; } } //// //... A a{'a', 2, 'b'}; B b{'a', 'b', 2}; cout<<"A:"<<endl; showmem((unsigned char*)&a, sizeof(a)); cout<<endl; cout<<"B:"<<endl; showmem((unsigned char*)&b, sizeof(b)); cout<<endl; |
结果如下:
|
A: 0 1 2 3 4 5 6 7 -------|-------|-------|-------|-------|-------|-------|-------|-------| 00| 0097 0000 0000 0000 0002 0000 0000 0000 08| 0098 0000 0000 0000 B: 0 1 2 3 4 5 6 7 -------|-------|-------|-------|-------|-------|-------|-------|-------| 00| 0097 0098 0000 0000 0002 0000 0000 0000 |
在 A
中,c_1 i
共占一个字,c_2
占一个字。这种将多个变量放入一个字中的动作,叫作 packing
。 而组成一个字时,补充不足的部分,叫 padding
。
如何知道某种类型的变量要对齐到哪个位置呢?它取决于一个类型的 alignment
如 int 类型的alignment 为4, 那么int 类型变量的地址必定为 4 的倍数。
继续阅读
在前一部分(从单例模式谈起(一))我们讨论单例模式时,谈到了 Double-Checked Locking
问题。我们讲, volatile
关键字并不能解决Double-Checked Locking 问题。这一节我们讨论与此相关的问题。
现代编译器为了提高程序的效率而对代码做了很多优化。这些优化大部分是有益的,但是也为多线程编程带来问题。
寄存器缓存
我们来看如下代码:
|
x = 0; Thread1 { lock(); x++; unlock(); } Thread2 { lock(); x++; unlock(); } |
x++
的值有锁保护,所以它是独占的,x++ 的行为不会被并发破坏。那么 x 的值必然为 2。 然而现实中并非一定如此:编译器有可能为了提高 x 的访问速度而将 x 放入线程的某个寄存器中,而线程的寄存器是线程私有的。 如果 Thread1 先获得了锁,那么程序的执行过程有可能是这样的:
- [Thread1] 读取 x 到寄存器 R1. (R1 = x = 0)
- [Thread1] R1++. unlock. 由于之后可能还会访问 x, 所以 Thread1 不将 R1 写回 x . (x = 0)
- [Thread2] 读取 x 到寄存器 R2 (R2 = x = 0)
- [Thread2] R2++
- [Thread2] 将 R2 写回 x (x = R2 =1)
- [Thread1] 在某个时候将 R1 写回 x (x = R1 = 1)
由此可见,即使正确的加了锁,也不能保证多线程安全。
继续阅读
什么是 Coredump
coredump 核心转储
,也称为 核心文件(core file)
是操作系统在进程收到某些 信号
而终止运行时,将此进程的地址空间以内容以及有关进程状态的其他信息写出的一个文件。这种信息往往用于调试。
程序员可以通过工具来分析程序运行过程中哪里出错了:Windows 平台用 userdump 和 WinDBG ,Linux 平台使用gdb, elfdump, objdump 等
Windows WinDBG
关于 windbg, 可以参考以下资料
Linux GDB
有些时候进程在crash的时候会产生 core 文件, 但我们却找不到 core 文件,我们需要使用 ulimit
进行一些设置, 这个命令是用来限制系统用户对shell资源的访问的。
ulimit -a
可以查看当前的设置
ulimit -c
可以设置 core 文件的上限,单位为区块(一般 1 block = 512 bytes) .其值为 0
时不写入 core, 为 unlimited
时不限制 core 文件大小。
需要注意, ulimit 只对当前会话有效。若想对所有会话生效, 需要在 /etc/profile 中进行配置。
源文件如下 test_vec.cpp :
|
#include <iostream> #include <vector> using namespace std; int main(){ std::vector<int> v{42}; cout<<v.at(5)<<endl; //Will crash, and coredump return 0; } |
编译运行时可能出现如下现象:
|
$ g++ -o t_vec ./test_vec.cpp $ ./t_vec terminate called after throwing an instance of 'std::out_of_range' what(): vector::_M_range_check: __n (which is 5) >= this->size() (which is 1) Aborted (core dumped) |
使用 gdb 打开来看:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
$ gdb t_vec core Core was generated by `./t_vec'. Program terminated with signal SIGABRT, Aborted. #0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:51 51 ../sysdeps/unix/sysv/linux/raise.c: No such file or directory. (gdb) i s #0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:51 #1 0x00007fbe77c7f801 in __GI_abort () at abort.c:79 #2 0x00007fbe782d4957 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6 #3 0x00007fbe782daab6 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6 #4 0x00007fbe782daaf1 in std::terminate() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6 #5 0x00007fbe782dad24 in __cxa_throw () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6 #6 0x00007fbe782d6855 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6 #7 0x000055a4f8cda08a in std::vector<int, std::allocator<int> >::_M_range_check(unsigned long) const () #8 0x000055a4f8cd9e9d in std::vector<int, std::allocator<int> >::at(unsigned long) () #9 0x000055a4f8cd9c11 in main () (gdb) |
从 gdb显示的栈信息来看,崩溃发生在 main 函数内的 vector::at 函数内,由 _M_range_check raise 。
如果我们在编译时使用了 -g 选项, 会得到更详细的信息
继续阅读
1. 调试宏
宏是预编译的,无法 print 宏的定义。但是如果配合 gcc, 我们还是可以有限地调试宏。
在 GCC 编译程序的时候,加上 -g3
参数,就可以调试宏了。
info macro mac_name
可查看宏定义,及位置
macro expand mac_expr
可查看宏展开的样子
示例:
|
#include <iostream> using namespace std; #define MY_ADD(a,b) (a * 2 + b) #define M 42 #define DM(x) (M+x) int main(){ int s = MY_ADD(2,3); cout<<s<<endl; cout<<DM(s)<<endl; return 0; } |
调试现场如下:
|
(gdb) i macro M Defined at /home/zr/code/use_gdb/src3/./test_macro.cpp:5 #define M 42 (gdb) macro expand DM(1) expands to: (42+1) |
2. 修改变量
两种方法:
print var_name=x
set var var_name=x
示例:
继续阅读
命令
GDB 是 Linux 下的命令行调试工具。
启动 GDB 有如下几种方式:
gdb <program>
直接启动执行程序
gdb <program> core
用gdb 同时调试一个可执行程序和core文件。core 是程序非法执行后 core dump 产生的文件
gdb <program> <PID>
指定进程, gdb会自动 attach 上去。program 应该在 PATH 环境变量中可以搜索得到。
常用的 gdb 命令如下
信息 info
info 可以简写成 i
info args
列出参数
info breakpoints
info break
i b
列出所有断点
info break number
i b number
列出序号为 number 的的断点
info watchpoints
i watchpoints
列出所在 watchpoints
info threads
列出所有线程
inifo registers
列出寄存器的值
info set
列出当前 gdb的所的设置
i frame
i stack
i locals
i catch
断点和监视 break
& watch
继续阅读
Cookie
指某些网站为了辨别用户身份而储存在用户本地终端(Client Side)上的数据,它是一种古老的技术, 由网景公司的前雇员卢·蒙特利在1993年3月发明。
Cookie 格式是一系列键值对, 以 ;
组合,如下
|
TBLkisOn=0; GeoIP=US:CA:Los_Angeles:34.05:-118.26:v4; mwPhp7Seed=9d0 |
当然, Cookie还有更多的内容,如创建时间,过期时间等,对应的域等等。一般而言,为了安全只允许页面访问该域下的Cookie.
根据 Cookie 的时效性可以将 Cookie 分为两类,一种是会话型Cookie (Session Cookie), 只保存于内存中, 当浏览器退出的时候,即清除这些 Cookie. 第二种是持续型 Cookie (Persistent Cookie),也就是当浏览器退出的时候仍然保留的Cookie.
Chromium 中Cookie操作的类结构如下所示:
其中 CookieStore
是主要的导出接口,CookieMonster
是重要的实现接口,它相当于是 Cookie 的管理器。它有几个作用:一是实现 CookieMonster
中的接口,二是报告前者的事件,如 Cookie 更新信息等,三是 Cookie对象(即 CanonicalCookie
) 的集合。
PersistentCookieStore
持久化类,SQLitePersistentCookieStore
是持久化的具体实现,负责实际的存储动作。
Chrome 的 Cookie使用 Sqlite存储,是位于 %AppData%\Local\Google\Chrome\User Data\Default
目录下的 Cookies
文件。
继续阅读
这一章谈 C++11 中引入的两种 “语法糖” .使用它们可以使得我们的代码更为简洁优雅。
委托构造函数
在同一个类中,一个构造函数可以调用另一个构造函数,这叫委托构造函数。这是 C++ 11 的新特性。
委托构造函数可以简化在每个构造函数中的重复代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
class B{ public: B():x_(0),y_(0),z_(0){ //Do something } B(int x): B(){ x_ = x; } B(int x, int y): B(x){ y_ = y; } void DoSomething(){} private: int x_; int y_; int z_; }; |
注意一点,委托构造函数在使用时不可以形成环:禁止套娃。
继续阅读
这一章聊一聊在面向对象的C++中,构造函数的调用顺序。
数据成员的构造顺序
一个类的数据成员的初始化顺序只与其在类中的声明顺序相关,与其它无关。
而析构时,如果成员是在堆中,析构顺序正好与构造时相反。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
|
class M { public: M(const char* msg) { cout << "M " << msg << endl; msg_ = new char[strlen(msg)+1]; msg_[strlen(msg)] = '\0'; strcpy(msg_, msg); } ~M() { cout << "~M " << msg_ << endl; delete msg_; } private: char* msg_; }; class A { public: A() : pm2_(new M("pm2")), m2_("m2"), pm1_(new M("pm1")), m1_("m1") {} private: M m1_; M m2_; M* pm1_; M* pm2_; }; int main() { A a; return 0; } |
类A的成员的构造顺序为: m1_, m2_, pm1_, pm2_
。析构时的顺序为 m2_, m1_
,由于 pm1_, pm2_ 不在堆中,所以它们的析构需要类A自己管理。
继续阅读