《程序员的自我修养》读书笔记

1、windows API中的 堆管理器
    HeapCreate:创建一个堆
    HeapAlloc:在一个堆里分配内存
    HeapFree:释放已经分配的内存
    HeapDestroy:摧毁一个堆
    但是实际上,系统申请堆空间是调用VirtualAlloc()(类似于linux下的brk()和mmap()系统调用),在windows下一个进程通常通过malloc()可以申请1.5G的堆空间,而在linux下可以申请到2.9G大小的堆空间。

2、windows下c/c++程序 入口函数 初始化流程( mainCRTStartup
    (1)初始化和OS版本有关的全局变量
    (2)初始化堆(直接调用HeapCreate创建堆)
    (3)初始化I/O:
            建立打开文件表;
            如果能够继承自父进程,那么从父进程获取继承的句柄;
            初始化标准输入输出
    (4)获取命令行参数和环境变量 
    (5)初始化C库的一些数据    
    (6)调用main并记录返回值
    (7)检查错误并将main函数的返回值返回
    即main函数不是c程序的入口,linux下的 __libc_start_main和windows下的mainCRTStartup才是真正的入口函数,他们完成进程运行的初始化工作,然后执行main函数,最后进行清理工作。

3、在c++里返回一个对象的时候,对象要 经过2次拷贝构造函数的调用才能完成返回对象的传递。一次拷贝到栈上的临时对象里,另外一次把临时对象拷贝到存储返回值的对象里。因此, 在c++程序中应尽量避免返回对象或struct
    可以通过返回值优化(Return Value Optimization)在某些场合减少一次对象拷贝过程。

4、一般来讲,应用程序使用的内存空间有如下“默认”区域:
    (1)栈:用于维护函数调用上下文,通常有数兆字节的大小;
    (2)堆:用来容纳应用程序动态分配的内存区域,当程序用malloc或new分配内存时,得到的内存来自堆;
    (3)可执行文件映像:存储可执行文件在内存里的映像;
    (4)保留区:对内存中受保护而禁止访问的内存区域总称;
    其中,在linux下动态链接库位于堆和栈之间(从0x40000000开始),而程序运行时出现“段错误”或者“非法操作,该内存地址不能read/write”的错误信息,通常是由于没有初始化栈上的指针造成的,或者访问了初始化不合理的指针。

5、 setjmplongjmp非局部跳转
#include <setjmp.h>
#include <stdio.h>

jmp_buf b;
void f()
{
longjmp(b, 1);
}

int main(int argc, char* argv[])
{
if(setjmp(b))
printf("World!");
else
{
printf("Hello ");
f();
}
return 0;
}

这段代码输出为:“Hello World!”,setjmp(b)返回0,所以首先打印出“Hello”,但是longjmp(b, 1)让程序返回(程序执行流回流)到setjmp返回的时刻,并返回longjmp指定的第二个参数,即1,因此程序打印出“World!”。 longjmp能够让程序“时光倒流”到setjmp返回的时刻

6、DLL显示运行时链接
与ELF类似,DLL也支持运行时链接,即运行时加载,windows提供以下3个API:
    (1) LoadLibrary(LoadLibraryEx),用来装载一个DLL到进程地址空间,功能类似于dlopen;
    (2) GetProcAddress,用来查找某个符号的地址,与dlsym类似;
    (3) FreeLibrary,用来卸载某个已经加载的模块,与dlclose类似;
    DLL的代码不是地址无关的

7、线程局部(私有)存储
    线程拥有自己的私有存储空间包括:栈、线程局部存储(Thread Locale Storage,TLS)、寄存器;
    可以通过 __thread(linux)或者 __declspec(thread)(windows)来定义一个线程私有的全局变量,例如: __thread int number; 这样每个线程都会拥有一个这个变量的副本,任何线程对该变量的修改都不会影响其他线程中该变量的副本。在具体实现时,将TLS变量放在PE文件中“.tls”段中;他是从堆中分配的一块空间,最多可以拥有 1088(1024+64)个显式TLS变量。windows提供了如下4个API进行显式TLS操作:TlsAlloc、TlsGetValue、TlsSetValue、TlsFree,linux下则为:pthread_key_create、pthread_getspecific、pthread_setspecific、pthread_key_delete。
    在windows下进行多线程开发时,尽量用 _beginthread()/_beginthreadex()/_endthread()/_endthreadex()这组函数来创建线程,在MFC中则用AfxBeginThread()和AfxEndThread(),避免出现CreateThread()和ExitThread()函数会出现内存泄露的情况。
    由于CreateThread API中可能会申请一块内存(_tiddata结构,保存线程的显式TLS数组),而ExitThread() API不会释放这块内存,因此尽量避免调用这组API 创建线程,特别是在静态链接的情况下,动态链接不存在问题。 在动态链接的CRT中,DllMain会释放掉_tiddata,静态链接则没有释放,出现内存泄露。
测试代码如下:
#include<windows.h>
#include<process.h>

void thread(void *a)
{
char* r = strtok("aaa", "b");
ExitThread(0);
}

int main(int argc, char* argv[])
{
while(1)
{
CreateThread(0, 0, (LPTHREAD_START_ROUTINE)thread, 0, 0, 0);
Sleep(5);
}
return 0;
}
动态链接的CRT(/MD, /MDd)不会出现问题,而静态链接(/MT, /MTd),内存使用量会不断的上升。

8、C++全局构造与析构
glibc实际上是调用的 __do_global_ctors_aux()函数进行初始化全局对象,该函数位于GCC提供的 crtbegin.o目标文件中,在 __CTOR_LIST__里面存放了所有全局对象的构造函数指针,依次执行这些构造函数即可完成全局对象的构造;在每个对象的构造函数中,还注册了一个析构函数(通过回调的方式,例如: atexit(__tcf_1);),最后析构函数会以构造函数相反的顺序执行,进行全局对象的析构。
编译器在链接目标文件时将编译时为每个编译单元生成的特殊函数(GLOBAL__I_Hw全局构造函数与注册析构函数)合并在一起,即每个目标文件的.ctors段会被合并为一个 .ctors段,拼接起来的.ctors段就成为了一个函数指针数组,每一个元素都指向一个目标文件的全局构造函数。
要想自己定义的函数在main函数之前执行,则可以通过手动将函数指针添加到.ctors段里即可:
     ctor_t __attribute__((section (".ctors"))) my_init_p = &my_init;  //my_init为自定义的函数
    或者
    void my_init(void) __attribute__((constructor))
    全局析构函数是通过 __cxa_exit()exit()函数中注册进程退出时的回调函数
MSVCRT 的入口函数调用_initterm()函数进行全局构造,通过指针__xc_a和__xc_z之间存储的函数指针,依次调用进行构造。所有的全局构造函数存放在 .CRT(每一个编译单元都会生成.CRT$XCU的组, 其中U表示user,编译单元在这个组中加入自己的全局初始化函数)段中,链接合并后按字母序依次排列;自己可以通过如下方式手动添加初始化代码,使其在main之前执行:
#include<iostream>

#define SECNAME ".CRT$XCG"
#pragma section(SECNAME, long, read);
void foo()
{
std::cout<<"hello, world"<<std::endl;
}

typedef void (__cdecl *_PVFV)();
__declspec(allocate(SECNAME)) _PVFV dummy[] = { foo };

int main()
{
return 0;
}
    MSVC CRT的全局析构通过 atexit()函数实现
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值