C++性能优化(八)——内存分配机制

一、操作系统内存布局

1、32位系统经典内存布局

Linux Kernel 2.6.7前版本采用的默认内存布局形式如下:

(1)32操作系统中,loader将可执行文件的各个段次依次载入到从0x80048000(128M)位置开始的空间中。应用程序能够访问的最后地址是0xbfffffff(3G)的位置,3G以上的位置是给内核使用的,应用程序不能直接访问。

(2)内存布局从低地址到高地址依次为:txet段、data段、bss段、heap、mmap映射区、stack栈区。

(3)heap和mmap是相对增长的,heap只有1G的虚拟地址空间可供使用。​

(4)stack空间不需要映射,用户可以直接访问栈空间,因此是利用堆栈溢出进行攻击的基础。

起始1GB地址为内核空间,随后是向下增加的栈空间和由0x40000000向上增加的MMAP地址;堆空间从底部开始,去除ELF、数据段、代码段、常量段后的地址,并向上增长。缺点是容易遭受溢出攻击,堆地址空间只有不到1GB。

2、32位系统默认内存布局

Linux Kernel 2.6.7版本后32位操作系统的默认内存布局方式如下:

在经典内存布局基础上增加了Random offset随机偏移,不容易遭受溢出攻击;堆地址向上增长,但MMAP向下增长,栈空间不是动态增长的,会受到限制;内存地址利用率较高。

栈自顶向下扩展,但栈有边界,因此栈大小有限制(ulimit  -s查看)。堆自底向上扩展,mmap映射区自顶向下扩展,mmap和heap是相对扩展,直至消耗尽虚拟地址空间中的剩余区域。

3、64位系统内存布局

64位操作系统的寻址空间比较大,沿用32位操作系统的经典内存布局,增加随机MMAP地址,防止溢出攻击。

二、操作系统内存管理机制

1、操作系统内存管理简介

内存管理自底向上分为三个层次:

(1)操作系统内核的内存管理。

(2)glibc层使用系统调用维护的内存管理算法。

(3)应用程序从glibc动态分配内存后,根据应用程序本身的程序特性进行优化,比如使用引用计数std::shared_ptr,内存池方式等等。
应用程序可以直接使用系统调用从内核分配内存,根据程序特性自己维护内存,但会大大增加开发成本。

2、操作系统内存管理机制

Linux Kernel内存管理的基本思想是内存延迟分配,即只有在真正访问一个地址的时候才建立地址的物理映射。Linux Kernel在用户申请内存的时候,只分配一个虚拟地址,并没有分配实际物理地址,只有当用户使用内存时,Linux Kernel才会分配具体的物理地址给用户使用。

对于大内存,通常不同的内存分配方式都是直接MMAP;对于小数据,则通过向操作系统申请扩大堆顶,操作系统会把内存分页映射到进程堆空间,再由malloc管理内存堆块,减少系统调用;free内存时,不同内存分配方式有不同策略,不一定会将内存还给操作系统,因此如果访问释放的内存并不会立即Run Time Error,只有访问的地址没有对应的内存分页才会。

对于heap操作,操作系统提供brk系统调用,c运行库提供sbrk库函数;对于mmap映射区操作,操作系统提供了mmap和munmap系统调用。

3、heap操作系统调用接口

#include <unistd.h>
int brk(void *addr);
void *sbrk(intptr_t increment);

当参数increment为0时,sbrk返回进程当前的brk值,increment为正数时扩展brk值,increment为负数时收缩brk值。

brk是系统调用,sbrk是库函数。C语言的动态内存分配基本函数是malloc,在glibc中malloc函数调用库函数sbrk,sbrk调用brk函数。brk系统调用只是简单的改变mm_struct结构体的成员变量brkd的值。

start_code和end_code是进程代码段的起始和结束地址、

start_data和end_data是进程数据段的起始和终止地址。

start_stack是进程堆栈段的起始地址。

start_brk是进程动态内存分配的起始地址(堆的起始地址)。

brk是进程动态内存分配当前的终止地址(堆的当前最后地址)。

4、mmap操作系统调用接口

#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
int munmap(void *addr, size_t length);

addr:映射区的开始地址。

length:映射区的长度。

prot:期望的内存保护标志。

flags:指定映射对象的类型,映射选项和映射页是否可以共享。

fd:有效的文件描述符。

offset:被映射对象内容的起点。​

mmap函数将一个文件或者其它对象映射进内存,文件被映射到多个页上,如果文件大小不是所有页大小之和,最后一个页不被使用的空间将会清零;munmap函数删除特定地址区域的对象映射。

三、内存分配器

1、Cache Line

为了减少内存访问延迟,CPU 芯片集成有内置Cache,Cache 被划分为 Cache Line,一般为64B。CPU 访问内存时,会首先将内存缓存在 Cache Line 中。 在多处理器系统中,每个CPU芯片都有自己的本地Cache,会导致数据多副本,带来一致性问题:多个 CPU 的 Cache Line 中有相同地址的内存。现代处理器一般使用 MESI 协议实现 Cache Coherence协议,会带来通讯耗时、总线压力、导致 Cache Line 抖动,影响性能。

避免Cache Line抖动问题方法如下:

(1)使用__declspec (align(64))声明变量起始地址按 Cache Line对齐。

(2)当使用数组或结构体时,不仅需要起始地址对齐,还需要 padding,使得数组元素或结构体大小为 Cache Line 倍数。

(3)避免多线程使用相近地址的内存,多使用局部变量。

2、Cache着色

现代CPU的Cache 映射策略有很多,如组相联、全相联、直接相联。不同地址的内存有可能映射到相同的 Cache Line(主要发生在地址对齐的情况,如不同对象的地址按照 page size 对齐),如果频繁交替访问映射到相同Cache Line的内存,就会造成 Cache Line抖动。Cache着色通过给对象地址增加 Cache Line 大小倍数的偏移,从而映射到不同的 Cache Line,来避免Cache Line抖动问题。

缓存着色为了充分利用大Cache(例如6M二级cache)而产生的纯软件技术,不需要任何硬件改动。

Cache着色实现在linux内核的VM管理部分,在为进程分配内存时调用。

Cache着色只有在cache是组相连(直接相连是组相连的一种特殊情况)结构时才能适用。

3、glibc内存分配器

mmap按照 page size 进行分配,一般是4KB,若每次分配时都调用会造成极大的内存浪费,并且性能不好。若由程序员自己通过系统调用管理Page,容易出错且性能不好,GLibC中提供了标准 malloc供程序员使用。

4、内存分配器目标

内存分配器的目标主要有2个:

(1)减少内存碎片

内存碎片包括内部碎片和外部碎片。内部碎片是已经分配但没有使用到的内存,比如需要 14字节,分配了 16字节,多余的 82字节就是内部碎片;外部碎片是内存中因大小不合适导致无法分配出去的内存,比如申请 16 字节内存,但内存分配器中8 字节内存将不会被分配。

(2)提高多线程性能

四、ptmalloc2简介

1、ptmalloc2简介

ptmalloc/ptmalloc2/ptmalloc3版本基于dlmalloc进行开发,

Wolfram Gloger在Doug Lea的基础上对dlmalloc进行了改进,使得ptmalloc可以支持多线程。Linux操作系统默认使用glibc malloc版本,即ptmalloc2,glibc-2.3.x中已经集成了ptmalloc2,目前ptmalloc最新版本为ptmalloc3。

ptmalloc内存分配器处于内核和用户程序之间,用于响应用户的内存分配请求,向操作系统申请内存,然后将内存返回给用户程序。为了保证高效,内存分配器一般都会预先分配一块大于用户请求的内并进行管理;用户释放掉的内存不会立即返回给操作系统,内存分配器会管理被释放掉的空闲空间以应对用户以后的内存分配请求。内存分配器不仅需要管理分配的内存块,还需要管理空间的内存块。当响应用户的请求时,内存分配器会首先在空闲空间中寻找一块合适的内存返回给用户,在空闲空间中找不到时才会重新分配一块新内存。

2、ptmalloc2多线程支持

dlmalloc内存分配器中只有一个主分配区,每次分配内存都必须对主分配区加锁,分配完成后再释放锁。在多线程的环境下,对主分配区的锁的竞争很激烈,严重影响内存分配效率。

ptmalloc内存分配器可以支持多线程,增加了非主分配区支持,主分配区和非主分配区形成一个环形链表进行管理,每一个分配区使用互斥锁保证多线程访问安全。每个进程只有一个主分配区,可以有多个非主分配区,ptmalloc根据系统对分配区的争用情况动态的增加非主分配区的个数,分配区的数量一旦增加就不会再减少。

主分配区可以访问进程的heap和mmap映射区域,即主分配区可以使用brk、sbrk、mmap向操作系统申请虚拟内存;非主分配区只能访问进程的mmap映射区域,非主分配区每次使用mmap系统调用向操作系统申请HEAP_MAX_SIZE(32位系统1M,64位系统64M)大小的虚拟内存,当用户向非主分配区请求分配内存时再分割成小块内存进行分配。

由于系统调用的效率比较低,直接从用户空间分配内存效率会比较高,因此ptmalloc只在必要情况下才会调用mmap系统调用向操作系统申请内存。

主分配区可以访问heap区域,如果用户不调用sbrk或者brk函数,分配程序就可以保证分配到连续的虚拟内存,因为一个进程只有一个主分配区使用sbrk分配heap区域的虚拟内存。如果主分配区的内存是通过mmap向系统申请的,当free内存时,主分配区会直接调用munmap将内存归还给操作系统。

当线程需要使用malloc函数分配内存空间时,线程会先查看线程的私有变量中是否已经存在有一个分配区。如果存在,尝试对分配区加锁,如果加锁成功就使用相应分配区分配内存;如果失败,线程就搜索环形链表试图获得一个没有加锁的分配区来分配内存;如果所有分配区都加锁,那么malloc会开辟一个新分配区,把新分配区添加到循环链表中并加锁,使用新分配区进行分配内存操作。

在释放操作中,线程试图获得待释放内存块所在的分配区的锁,如果分配区正在被其它线程使用,则需要等待其它线程释放分配区的互斥锁后才可以进行释放操作。​

为了支持多线程,多个线程可以从同一个分配区中分配内存,ptmalloc假设线程A释放掉一块内存后,线程B会申请类似大小的内存,但A释放的内存跟B需要的内存不一定完全相等,可能有一个小的误差,就需要不停地对内存块作切割和合并,因此分配过程中可能产生内存碎片。

3、ptmalloc2缺点

(1)ptmalloc收缩内存从top chunk开始,如果与top chunk相邻的chunk不能释放,top chunk以下都不能释放。

(2)多线程锁开销大,需要避免多线程频繁分配释放。

(3)多线程使用内存不均衡时,容易导致内存浪费。

(4)每个chunk至少8字节的开销很大。

(5)不定期分配长生命周期的内存容易造成内存碎片,不利于回收。

五、ptmalloc2 BINS架构

1、chunk简介

若每次申请内存都调用sbrk、brk、mmap,那么每次都会产生系统调用,影响性能,并且容易产生内存碎片,因为堆是从低地址到高地址,如果高地址的内存没有被释放,低地址的内存就不能被回收。
ptmalloc使用内存池管理方式,采用边界标记法将内存划分成很多块,从而对内存的分配与回收进行管理。为了高效分配内存,ptmalloc会预先向操作系统申请一块内存供用户使用,当申请和释放内存时,ptmalloc会对相应内存块进行管理,并通过内存分配策略判断是否将其回收给操作系统,使用户申请和释放内存更加高效,避免产生过多的内存碎片。

用户请求分配的内存在ptmalloc中使用chunk表示, 每个chunk至少需要8个字节额外的开销。 用户释放掉的内存不会马上归还操作系统,ptmalloc会统一管理heap和mmap区域的空闲chunk,避免了频繁的系统调用。

struct malloc_chunk {
  INTERNAL_SIZE_T      prev_size;    /* Size of previous chunk (if free).  */
  INTERNAL_SIZE_T      size;         /* Size in bytes, including overhead. */
  struct malloc_chunk* fd;           /* double links -- used only if free. */
  struct malloc_chunk* bk;
  /* Only used for large blocks: pointer to next larger size.  */
  struct malloc_chunk* fd_nextsize;  /* double links -- used only if free. */
  struct malloc_chunk* bk_nextsize;
};

prev_size: 如果前一个chunk空闲,表示前一个chunk的大小;如果前一个chunk不空闲,字段无意义。 
size:当前chunk的大小,并且记录了当前chunk和前一个chunk的一些属性,包括前一个chunk是否在使用中,当前chunk是否是通过mmap获得的内存,当前chunk是否属于非主分配区。 
fd和bk:指针fd和bk只有当chunk空闲时才存在,用于将对应的空闲chunk块加入到空闲chunk块链表中统一管理,如果chunk 块被分配给应用程序使用,chunk块被从空闲chunk链表中拆出,那么这两个指针也就没有用了,所以也当作应用程序的使用空间,而不至于浪费。 
fd_nextsize和bk_nextsize:当前chunk存在于large bins 中时, large bins中空闲chunk按照大小排序,但同一个大小的 chunk可能有多个,增加fd_nextsize和bk_nextsize字段可以加快遍历空闲chunk ,并查找满足需要的空闲chunk, fd_nextsize 指向下一个比当前chunk大小大的第一个空闲chunk, bk_nextsize指向前一个比当前chunk大小小的第一个空闲 chunk 。

使用中的chunk结构如下:

chunk指针指向chunk开始的地址;

mem指针指向用户内存块开始的地址。

P=0时,表示前一个chunk为空闲,prev_size才有效;P=1时,表示前一个chunk正在使用,prev_size无效;P主要用于内存块的合并操作。ptmalloc分配的第一个块总是将P设为1,以防止程序引用到不存在的区域。​

M=1为mmap映射区域分配;M=0为heap区域分配。

A=1为非主分区分配;A=0为主分区分配。

空闲chunk结构如下:

空闲的chunk会被放置到空闲的链表bins上。当用户申请分配内存时,会先去查找空闲链表bins上是否有合适的内存。

fp和bp分别指向前一个和后一个空闲的chunk。

fp_nextsize和bp_nextsize分别指向前一个空闲chunk和后一个空闲chunk的大小,用于在空闲链表上快速查找合适大小的chunk。

2、BINS架构简介

ptmalloc将相同大小的chunk用双向链表链接起来,称为bin。ptmalloc共维护128个bin,并使用一个数组来存储bin。

数组中第1个bin为unsorted bin,如果被用户释放的chunk大于max_fast或者fast bins中的空闲chunk合并后,chunk首先会被放到unsorted bin队列中。如果unsorted bin不能满足分配要求,ptmalloc会将unsorted bin中的chunk加入bins中,然后再从bins中继续进行查找和分配过程。unsorted bin是bins的一个缓冲区,可以加快内存分配速度。

数组中第2个至第64个的bin称为small bin,同一个small bin 中的chunk具有相同的大小,相邻的small bin中的chunk大小相差为8字节。

数组中第65个开始bin称为large bin。large bins中的每一个 bin分别包含了一个给定范围内的chunk,其中chunk按大小序排列,相同大小的chunk按照最近使用顺序排列。

3、Fast Bins

程序在运行时会经常需要申请和释放小块内存空间。当内存分配器合并相邻的若干chunk后,可能立即会有另一个小块内存的请求,内存分配器需要从大块空闲内存中分割出一块内存,效率低下,因此ptmalloc在分配过程中引入了fast bins。
fast bins是bins的高速缓冲区,每个fast bin是空闲chunk的单链表,采用单链表是由于fast bin中增删chunk都发生在链表的前端。fast bins中包含7个fast bin,fast bin中的chunk大小以8字节递增,分别为16、24、32、40、48、56、64。 

当用户申请分配不大于max_fast(默认值64字节)内存块时,ptmalloc首先会先到fast bins中相应chunk尺寸的fast bin寻找是否有合适chunk,在fast bin中被检索出的第一个chunk将被摘除并返回给用户;当用户释放一块不大于max_fast(默认值64字节)的chunk时,默认会被放到fast bins中chunk尺寸大小的fast bin的前端,除非特定情况,两个相邻空闲chunk并不会被合并成一个空闲chunk,不合并可能会导致碎片化问题,但可以大大加速释放过程。

4、Unsorted Bin

Unsorted Bin存储在bins数组的第1个,是bins的缓冲区,用于加快内存分配速度。当用户释放的chunk大于max_fast或者fast bins中合并后的chunk都会首先进入unsorted bin上。Unsorted Bin用双向链表管理chunk,chunk无大小限制,任何大小chunk都可以添加进Unsorted Bin。Unsorted Bin提供了ptmalloc重复使用最近释放的chunk,因此内存的分配和释放会更快。 
用户申请分配内存时,如果在fast bins中没有找到合适的chunk,则ptmalloc会先在Unsorted Bin中查找合适的空闲chunk;如果没有合适的chunk,ptmalloc会将unsorted bin上的chunk放入bins上,然后继续到bins上查找合适的空闲chunk。 

5、Small Bins

小于512字节的chunk即small chunk,保存small chunk的bin即small bin。bins数组从第2至第64的63个bin为Small Bin,Small Bins中相邻bin之间相差8个字节,同一个Small Bin中的chunk具有相同大小,第0个Small Bin中的chunk大小为16字节,第62个Small Bin中的chunk大小为512字节。
每个Small Bin是一个空闲chunk的双向循环链表,释放掉的chunk添加在链表的前端,而分配出的chunk则从链表后端摘除并返回给用户。 
分配chunk时,从相应chunk大小的small bin中摘除最后一个chunk并返回给用户;释放chunk时,ptmalloc会检查相邻chunk是否空闲,若是则将相邻chunk从所属的small bin中摘除并合并成一个新chunk,新chunk会添加在unsorted bin的前端;合并chunk消除了碎片化的影响但减慢了释放速度。 

6、Large Bins

大小大于等于512字节的chunk即Large Chunk,保存Large Chunk的bin即Large Bin,位于bins数组中small bins后。Large Bins中的每一个bin分别包含了一个给定范围内的chunk,其中的chunk按大小递减排序,大小相同则按照最近使用时间排列。 

Large Bins包含63个bin,每个bin中的chunk大小不是一个固定的等差数列,而是分成6组;每组bin中chunk数量是一个固定的等差数列,依次为32、16、8、4、2、1,chunk大小公差依次为64B、512B、4096B、32768B、262144B等。

7、Top Chunk 

top chunk是分配区的顶部空闲内存,当bins上都不能满足内存分配要求时,就会在top chunk上进行分配。 

当top chunk大小比用户所请求大小还大的时候,top chunk会分割为两个部分:User chunk(用户请求大小)和Remainder chunk(剩余大小),Remainder chunk会成为新的top chunk;当top chunk大小小于用户所请求chunk大小时,top chunk会通过sbrk(主分配区)或mmap(非主分配区)系统调用来扩容。 ​

主分配区是唯一能够映射进程heap区域的分配区,可以通过sbrk来增大和收缩进程heap的大小。ptmalloc在开始的时候会预先分配一块较大的空闲内存。主分配区的top chunk在第一次调用malloc时会分配一块空间作为初始化的heap。

非主分配区会预先从mmap区域分配一块较大的空闲内存模拟 sub-heap,通过管理sub-heap来响应用户的需求,因为内存是按地址从低向高进行分配的,在空闲内存的最高处存在着一块空闲 chunk,即top chunk。当fast bin和bins都满足不了用户的内存分配需求时,ptmalloc会从top chunk分出一块内存给用户,如果top chunk空间不足,会重新分配一个sub-heap,将top chunk迁移到新的sub-heap上。在分配过程中,top chunk的大小随着切割动态变化。

主分配区是唯一能够映射进程heap区域的分配区,可以通过sbrk来增大或收缩进程heap大小。top chunk在heap的最上面,如果申请内存时,top chunk空间不足,ptmalloc会调用sbrk将进程heap的边界brk上移,然后修改top chunk的大小。

8、mmaped chunk 

当需要分配的chunk足够大,fast bins和bins都不能满足要求,甚至top chunk都不能满足分配需求时,ptmalloc会使用mmap来直接使用页映射机制来将页映射到进程空间,放到mmaped chunk上,当释放mmaped chunk上内存的时候会直接交还给操作系统。

mmap分配阈值默认为128KB,分配阈值可以动态调整。如果开启mmap分配阈值的动态调整机制,并且当前回收的chunk大小大于mmap分配阈值,将mmap分配阈值设置为chunk的大小,将mmap收缩阈值设定为mmap分配阈值的2倍。

9、Last remainder chunk 

Last remainder chunk是一种特殊chunk,不会在任何bins中找到。当需要分配一个small chunk,但在small bins中找不到合适的chunk,如果last remainder chunk的大小大于所需要的small chunk大小,last remainder chunk被分割成两个chunk,其中一个chunk返回给用户,另一个chunk变成新的last remainder chunk。

六、常用内存分配器算法

1、Dynamic memory allocation

首先分配一整块内存,然后按需从中分配。一般会在分配出的内存前面保存metadata,并会维护freelist用于查找空闲内存,通常存在比较严重的外部碎片。

2、Buddy memory allocation

Binary buddy algorithm从一块内存中分配,但不是按需分配大小,而是将内存不断分成一半,直到到达目标大小或者下界。在释放时,会和分裂的且空闲的进行合并。一般会用有序结构如红黑树来存储不同大小的 buddy block,分配和合并时可以快速查找合适的内存。

Binary buddy algorithm算法能够有效减少外部碎片,但内部碎片很严重,最严重会带来 50% 的内部碎片。

3、Slab allocation

对象的初始化和释放往往比内存的分配和释放代价大,slab会提前分配一块内存,然后将连续内存划分为大小相同的 slots,使用相应的数据结构记录每个slots 的分配状况,如 bitmap。当需要分配时,就查找对应大小的 slab,分配出一个空闲 slot,而释放时把这slot 标记为空闲。

slab 的 size classes 会影响碎片的产生,size classes太稀疏会导致内部碎片,size classes太密集会导致外部碎片。

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 《高效编程:内存与性能优化》是一本非常实用的技术书籍,旨在为程序员提供优化内存使用和程序性能的各种实用技巧和建议。 本书主要分为两部分,一部分探讨了内存管理的各种技术、原理和实际应用,包括操作系统内存管理、动态内存分配、垃圾回收、内存泄露检测、内存分析工具等。另一部分则涵盖了程序性能优化的相关内容,包括算法优化、编译器优化、多线程并发控制、IO操作优化等。 本书的亮点在于它不仅提供了各种理论知识,更重要的是提供了大量实际案例和实践经验,实用性非常强。针对某些常见问题,例如内存泄露、内存碎片、多线程死锁等,本书提供了详细的解决方案和案例,可供开发者参考和使用。 另外,本书也介绍了一些常用的开发工具和库,例如Valgrind、GDB、Boost等,这些工具和库可以为程序员提供更好的内存管理和性能优化支持。 总而言之,《高效编程:内存与性能优化》是一本非常实用的技术书籍,适合各类程序开发者参考和学习。它提供了全面的理论知识和实践经验,对于优化程序的内存使用和性能表现至关重要。阅读本书可以帮助程序员更好地把握程序开发中的重要技术点,提高程序的可靠性和性能表现,是一本非常值得推荐的技术参考书。 ### 回答2: 《高效编程:内存与性能优化》是一本针对程序员提高代码效率的教材,主要讲解如何优化内存使用和提高程序性能。本书内容涵盖了很多实用的技巧和方法,非常有针对性和实用性。 在内存方面,本书从一开始介绍了内存的基本概念,然后针对常见的内存问题,如内存泄漏、内存碎片、内存分配等等进行了详细的讲解。此外,还提供了一些实用的工具和方法,如内存分析工具、内存池等等,帮助读者提高内存使用的效率。 在性能方面,本书介绍了一些常见的性能瓶颈,如CPU、IO、网络等等,然后提供了一些相应的优化方法,如缓存、异步IO、多线程等等,让程序员可以更好地利用计算机资源,提高程序的执行效率。 总的来说,《高效编程:内存与性能优化》是一本非常实用的书籍,可以帮助程序员更好地理解和应用内存和性能优化的方法和工具,进而提高代码效率,开发出更加优秀的软件产品。 ### 回答3: 本书主要是以C语言为主题,讲解如何进行高效编程,优化内存与性能。首先,作者详细介绍了内存管理方面的知识,例如从内存的角度解释了变量的本质,指针数组的特点,动态内存分配的方式,以及内存泄漏等问题。其次,本书针对性能进行了多方面的优化,例如从一些小的方法入手,如缓存算法、位运算等,到牵涉更复杂的框架进行优化,比如线程、异步编程等。最后,本书通过大量的案例学习,将讲解内容与实际应用相结合,以帮助读者更加深入地理解和熟悉C语言的高效编程方式,使读者可以更好的应用于实际项目中。 总之,本书旨在帮助读者掌握C语言编程中的关键技能,提高代码的内存使用和性能优化水平,深度挖掘C语言的内在原理,让读者能够编写出复杂而高效的程序。对于对编程有兴趣的学生和工程师,本书是一个不错的选择。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值