IT技术博客大学习 共学习 共进步
全部 移动开发 后端 数据库 AI 算法 安全 DevOps 前端 设计 开发者

标签:C++

共 112 篇相关文章

IT 累计浏览 206

C++ 中的 main 定义

这篇讲的是 C++ 最新标准里一个看似微小却影响行为的细节调整:关于 `main` 函数的定义。 作者直接指出了核心变化——根据最新的 C++ 标准草案,程序入口函数 `main` 不再允许被 `extern "C"` 这样的 linkage-specification 修饰。文章引用了标准原文,明确了这一点。对于很多习惯了在某些项目或编译器下这样写来避免名字修饰的开发者来说,这可能会造成困惑或编译错误。 文章进一步解释了背景:在旧标准和实际编译器实现中,`main` 作为程序入口点,其链接约定有着特殊的隐含处理。允许用 `extern "C"` 修饰是一种非正式的宽容行为,但新标准明确了这一行为是不符合规定的。文章通过展示错误用法与正确用法的代码对比,清晰地指明了正确做法——只需遵循基本的 `main` 函数签名即可。 这提醒我们,依赖编译器的“非标准特性”或历史宽容行为存在风险。随着标准演进,这些模糊地带正在被澄清。对于需要严格符合标准的代码,尤其是跨平台项目,留意此类细节更新很有必要。

IT 累计浏览 2,007

内存的惰性初始化

这篇文章从一个 MMO 服务器压力测试的优化场景切入,探讨了当使用 A* 算法在一个巨大的三维网格(10MB 内存)中寻路时,如何解决初始化开销过大的矛盾。 实现者为避免每次调用都 memset 清零,采用在格点中记录版本号的技巧,实现了“用到时再判断”的惰性逻辑,但这依然需要全局保留这块内存。作者从更高层面指出,这本质上是一个用平坦内存空间模拟稀疏矩阵的权衡问题。 为此,他设计了一套惰性初始化的内存结构:以 64 字节(cacheline 大小)为单位划分内存,仅用一个二级标记树(总开销约 20KB)来记录哪些段落已被初始化。访问时检查标记,按需清零。这样,绝大多数未被访问的内存区域永远不会被初始化,将时间开销降至接近于零,同时空间代价极小。 文章结尾更提出了一个巧妙的延伸思路:对于这种障碍物静态且局部的寻路,与其在运行时寻路,不如用巨大的预计算空间将路径全部存储下来,实现 O(1) 查询。这为解决此类特定问题提供了不同的架构视角。

IT 累计浏览 2,381

游戏引擎中的资源生命期管理问题

这篇讲的是游戏引擎开发中资源生命期管理的架构演进与权衡。作者从团队实际遇到的问题出发:最初依赖Lua的垃圾回收,但其触发时机不可控,易造成图形渲染卡顿;而随着场景复杂化,单帧内过多的资源API调用甚至导致渲染后端崩溃。 为应对这些问题,团队先后尝试了经典的引用计数方案,但作者认为在Lua框架与内存受限的移动设备上,单纯基于“是否还有引用”来决定资源释放并非最优解。他将资源分为两类:一类是可从磁盘重新加载的IO资源(如贴图),另一类是依赖上下文、难以重建的运行时生成资源。 针对第一类资源,作者提出不应被引用关系束缚,而应遵循LRU原则,允许引擎随时释放长期未用的内存。对于第二类资源的处理灵感则来自imgui:采用“每帧主动创建”的方式来规避复杂的重建回调,再将结果缓存。这样一来,所有资源都可以统一由LRU策略管理,在内存压力下安全淘汰。文章梳理了从全量保留、Lua GC、引用计数到统一LRU缓存的完整思考路径,展现了从具体约束中提炼通用架构的工程智慧。

IT 累计浏览 2,941

Lua C API 的正确用法

这篇讲的是在C/C++宿主程序中嵌入Lua时,最容易被忽视的陷阱:如何正确处理Lua的异常(error)机制。文章指出,很多开发者会忽略C API调用可能抛出异常这一点,导致程序出现未定义行为甚至直接崩溃。 作者从基础讲起,强调所有可能出错的API调用(如lua_tostring)都应被lua_pcall或lua_resume保护起来。文章用一个创建虚拟机的简单代码示例,清晰地展示了即使luaL_openlibs也可能因未捕获异常而带来风险,并推荐将核心逻辑封装为lua_CFunction再通过lua_pcall调用。 进阶部分深入讨论了在C++或多语言环境(如Unity的mono)中,Lua的异常机制(基于longjmp/throw)与宿主语言异常系统(如C++ RAII、.NET CLR)的协同难题。文章警告,直接用宿主语言的异常处理(如C++的try-catch)去捕获Lua API异常会破坏虚拟机状态,因此必须精心设计接口,将Lua与宿主视为两个通过消息通信的独立运行时,而非共享异常上下文。 最后,文章提到了在C扩展中初始化对象字段、管理栈空间等实用技巧,并附上了作者为解决跨语言交互问题而编写的示范代码。全文聚焦于“正确性”而非性能,对于任何需要深度集成Lua的开发者来说,这是一份避开关键坑点的实用指南。

IT 累计浏览 4,788

C++线程池实现原理

这篇讲的是C++线程池中一个核心但常被忽略的机制——闭包(Closure)是如何工作的。作者没有一开始就堆砌复杂概念,而是从一段注释丰富的简单示例代码切入,展示了使用线程池的标准写法。 然后,他带读者聚焦到那个让人困惑的`NewClosure`函数。文章的巧妙之处在于,它揭示了这个“闭包”其实是一个简单的模板对象。其核心思路是:通过模板类`ObjClosure1`,将对象指针、成员函数指针以及参数值,这三样东西打包存储起来。当线程池的工作线程取出这个任务时,调用的`Run()`方法内部,再通过`(p_->*fun_)(arg1_)`这样的C++成员函数指针调用语法,把这些元素重新组合起来执行。 文章把这个过程比作“打包”与“拆解执行”,解释得清晰直白。作者的目标是让初学者看完后,从“不明觉厉”变为“原来如此”。对于想了解线程池任务封装原理,却又对模板和函数指针感到头疼的C++开发者来说,这是一篇很好的入门拆解。

IT 累计浏览 4,669

C++之stl::string写时拷贝导致的问题

这篇讲的是作者在实现数据结构序列化时,因滥用 `std::string` 作为缓冲区而踩到的一个隐蔽陷阱。具体来说,作者直接通过 `(char*)s.c_str()` 获取内部指针并使用 `fread` 写入数据,这在多数情况下看似高效且可行。然而,当代码中发生类似 `string s2 = s;` 的拷贝操作后,再对 `s2` 进行同样的 `fread` 操作,原本期望保持不变的 `s` 内容却意外被篡改了。 问题的根源在于部分 STL 实现(如旧版 GCC)采用了“写时拷贝”(copy-on-write)机制来优化 `string` 的拷贝性能。此时,拷贝操作仅共享底层指针,而非进行深拷贝。这就导致通过强制类型转换修改共享内存区的数据时,所有持有该指针的 `string` 对象都会受到影响。文章清晰对比了“写时拷贝”与“立即拷贝”(eager-copy)两种策略的差异与适用场景,并指出该 bug 因其延迟显现的特性而极难定位。 作者最终总结道,尽管将 `string` 作为临时缓冲区的写法在网络上屡见不鲜,但在“写时拷贝”的实现下,这种用法存在严重隐患。对于需要直接操作内存的场景,开发者应意识到 STL 容器的这些实现细节,或考虑使用更明确的缓冲区类型,以避免类似的陷阱。

IT 累计浏览 4,642

在Linux进行IO的正确姿势

很多C/C++程序员在做网络编程时,习惯使用封装好的库,却可能忽略了底层IO操作的一个常见陷阱。这篇文章指出,许多人对read()/write()函数的错误处理并不到位。 作者从一个典型的错误代码示例出发:检查返回值为-1或0就认为完成了处理。但这忽略了关键的errno判断,尤其是EINTR(系统中断)和EAGAIN(非阻塞IO暂无数据)这两种情况。文章进一步展示了,仅仅判断errno还不够,正确的姿势是将IO操作放在一个while循环中,以便在发生可恢复的中断时进行重试,而非直接退出。 文中强调,一个完善的IO处理逻辑必须能应对操作系统的瞬时状况,并结合了select/epoll等IO多路复用机制。最后,文章建议读者参考sim框架的开源代码,学习成熟的IO处理模式。

IT 累计浏览 2,746

排错经历:全局变量被多次析构

这篇讲的是一个C++服务程序在退出时崩溃的排查过程。作者团队发现服务每次正常退出都会触发core dump,堆栈显示崩溃发生在exit()函数析构全局变量时,提示“double free or corruption (fasttop)”,指向一个std::string全局变量。 排查过程相当硬核。由于服务器使用定制glibc,缺乏调试符号,作者只能在不完整的栈上“黑灯瞎火”地工作。核心难点在于,崩溃发生在main函数返回之后,且该std::string在exit中被析构数百次,难以直接定位。作者尝试在string的析构函数中打断点,却发现this指针已被编译器优化掉,常规条件断点也无法设置。 最终,作者绕到析构函数的底层实现(_M_dispose)下断点,并通过分析内存布局,找到了引用计数成员(_M_refcount)的偏移地址。他巧妙地设置了一个基于寄存器的条件断点(`if *((int*)$edi+4)!=-1`),成功捕获到了那个引用计数异常(为-2)的string对象。通过自定义的xxd命令打印内存,最终定位到了那个出问题的全局变量。整个过程展示了在调试信息匮乏的条件下,如何结合对内存结构和编译器行为的深刻理解,一步步从core dump反向追踪到问题根源的扎实技巧。

IT 累计浏览 3,505

STL笔记之hashtable

这篇讲的是SGI STL中经典hashtable容器的源码实现。作者从自己动手实现一个简单hashtable的经历切入,发现与STL源码的思路惊人相似,由此展开对底层机制的剖析。 文章深入讲解了SGI版本hashtable的核心设计:它采用开链法解决冲突,节点通过链表串联在bucket中。关键实现的巧妙之处体现在两处:一是迭代器逻辑,当前bucket遍历完毕后,能自动跳转到下一个非空bucket,保证了前向遍历的连贯性;二是容器容量的管理,预置了一张精心计算的素数表来动态选择桶的数量,这能有效降低哈希冲突概率,提升查找效率。 通过拆解节点定义、迭代器操作符和容器初始化等关键源码,文章清晰地展现了hashtable从数据结构到算法选择的完整实现脉络,有助于读者真正理解这个高效容器背后的工程权衡与精巧构思。

IT 累计浏览 2,437

C++ 传参时传内置类型时用传值(pass by value)方式效率较高

这篇文章从《Effective C++》的经典条款出发,深入探讨了一个C++开发者常感困惑的问题:函数传参时,应该用传值(pass by value)还是传引用(pass by reference)?作者指出,这个选择并非一概而论,而是取决于参数的类型——文章特别对比了内置类型(如int、指针)与自定义类型之间的行为差异。 为了验证这一结论,文章提供了清晰的代码示例和关键的汇编代码对比。核心发现在于:对于int这类内置类型,传值(`int f(int i)`)编译生成的x86汇编指令更简洁,参数直接通过寄存器传递,效率最高;而传常量引用(`int g(const int &i)`)则多了一次内存间接访问的步骤,尽管微小但在极热路径上可能产生影响。文章也点明了反向情况:当面对存在构造与析构开销的自定义类型时,传引用才能避免不必要的复制成本。此外,STL中的迭代器与函数对象本质上是指针,因此遵循内置类型的规则。 作者通过这次代码级的验证,不仅重申了一个有效的实践准则,更通过底层视角加深了理解:效率的考量必须结合类型特性。这提醒我们,在追求代码“优雅”的同时,也不应忽视特定场景下符合硬件行为的朴素写法所带来的切实性能收益。

IT 累计浏览 4,989

struct与class区别联系

这篇讲的是C和C++中`struct`这个看似相同的关键字,其实内核大不相同。作者开篇就指出了核心区别:C中的`struct`是“原生”的,仅仅用来将一组属性打包成一个整体,没有任何面向对象(OO)的特性。而C++中的`struct`则是在此基础上做了深度扩展,它完全兼容C的用法,但更重要的是具备了OO特性——事实上,C++中`class`能干的事情,`struct`几乎都能干,包括继承和多态。 文章通过一个直观的代码示例验证了这一点:如果在纯C环境下(例如用GCC的C模式编译),在`struct`内部直接定义成员函数会导致编译报错;但同样的代码在C++中则毫无问题。这生动地说明了“原生”与“扩展”的差异。 那么,在C++中`struct`和`class`到底还有何区别?唯一的、关键的不同在于默认的访问权限:`struct`默认是`public`,而`class`默认是`private`。这个细微差别决定了代码风格和设计意图。通常,我们用`struct`来封装纯数据的聚合体,而用`class`来定义那些需要隐藏实现细节、提供接口的抽象数据类型。这篇小文通过对比和代码解析,清晰地帮你厘清了这个C++程序员常会遇到的疑惑。

IT 累计浏览 2,054

用MeCab打造一套实用的中文分词系统

这篇讲的是如何将原本为日文设计的高性能分词器 MeCab,成功改造为一个实用的中文分词系统。作者从 MeCab 基于条件随机场(CRF)的核心优势和中文资料匮乏的现状出发,分享了一次成功的“跨界”实践。 文章的核心方案是,参考一篇关键的日文博客和官方文档的训练指南,结合微软研究院的 backoff2005 中文语料来完成训练。作者详细记录了从准备符合 MeCab 格式的种子词典(例如,词典条目为 `义演,0,0,0,0,0,0`)到利用脚本进行参数估计的完整流程。文中提到,最终得到的系统不仅速度快(实测近 2MB/s),还支持 N-best 输出和用户词典定制等实用功能。 这篇文章的价值在于,它并非停留在理论介绍,而是提供了一条可操作的路径。通过作者在 Mac 环境下的亲测记录,读者可以了解如何利用一个强大的现有框架,为自己的中文 NLP 任务快速搭建起一个高性能的基础工具。

IT 累计浏览 6,536

萃取(traits)编程技术的介绍和应用

这篇讲的是C++中一项优雅而实用的泛型编程技巧——萃取(traits)技术。作者从STL中迭代器与算法解耦的需求出发,首先展示了一个核心矛盾:为了让统一的算法(如find)既能作用于自定义容器迭代器,又能兼容原生指针(如int*),我们需要一种机制来统一获取迭代器所指对象的类型信息。文章清晰地介绍了如何通过`iterator_traits`这个中间层,结合模版偏特化来解决这一问题,使`int*`这样的原生指针也能被正确萃取出`value_type`。 接着,文章将这一思想从迭代器扩展到了更广阔的“类型萃取”领域。作者以SGI STL的`__type_traits`为例,说明了如何利用萃取技术探测类型特性(如是否有平凡的构造函数、析构函数)。通过为内置类型(如int)提供特化版本,算法(如copy)就能在运行时选择最高效的实现路径——比如直接进行内存拷贝而非调用复杂的构造函数,从而显著提升性能。 文章从具体问题切入,逐步抽象出“特性萃取”的通用编程模式,最后落脚于其对C++泛型库性能优化的实际价值,展示了如何通过编译期的类型信息提取来弥补语言本身的局限。

IT 累计浏览 2,393

小心 int 乘法溢出!

这篇讲的是 C/C++ 编程中一个隐蔽却危害巨大的陷阱:32 位 `int` 类型在进行乘法运算时可能发生的溢出问题。作者从一个实际场景切入——试图用 `malloc()` 分配 3000MB 内存时,代码 `mb * 1024 * 1024` 由于 `int` 只有 32 位,其计算结果早已溢出为负值 `-1073741824`。这个负值作为 `size_t`(64位系统中是64位无符号整数)参数传入 `malloc`,会被解释成一个天文数字(约 1844 亿亿),导致分配请求远超预期,引发程序崩溃或内存损坏。 文章特别点明,这种错误非常具有迷惑性,因为无论操作系统是 32 位还是 64 位,`int` 通常是 32 位的。作者以亲身在 SSDB 项目中多次踩坑的经历作为佐证,强调了问题的普遍性与现实危害。核心告诫在于:当运算结果可能超出 `int` 范围,尤其是作为内存大小等参数时,必须显式地进行类型转换或选用合适的数据类型(如 `size_t`),以确保计算的正确性与安全性。对于处理大数据的系统开发者而言,这是个值得时刻警惕的细节。

IT 累计浏览 2,115

关于回调函数和this指针探讨

这篇讲的是C++回调函数中一个经典又微妙的陷阱:当类的非静态成员函数作为回调时,其隐含的this指针如何传递。作者从C语言的回调机制出发,对比了Java等面向对象语言直接注册对象的不同,聚焦于C++必须面对函数指针的问题。 文章的核心是解决一个实际困境:我们希望成员函数既能作为回调,又能访问类的非静态成员。作者梳理并对比了几种可行方案。最直接的方法是使用静态成员函数,但它不能直接访问非静态成员。由此引出两种变通:一是将对象指针存在全局变量中,但这破坏了封装;二是将this指针作为参数显式传入静态回调函数,这是目前的主流做法。作者也尝试了直接将非静态成员函数指针强转为普通函数指针,但编译和运行时都会出错,这揭示了this指针并非简单地作为第一个参数在栈上传递。 最终,文章回归到最实用且正统的解决方案:将回调声明为静态成员函数,并在注册时将this指针作为参数传入。这种方式在封装性和易用性之间取得了平衡。作者通过代码实例逐步演进,清晰地展示了从“能工作”到“更优雅”的优化路径,对于理解C++对象模型和底层回调机制很有启发。

IT 累计浏览 3,576

用三段 140 字符以内的代码生成一张 1024×1024 的图片

这篇讲的是 StackExchange 上一个名为 “Tweetable Mathematical Art” 的硬核编程挑战。参赛者需要仅用三段不超过 140 字符(相当于一条推文长度)的 C++ 代码,生成一张 1024×1024 像素的彩色图片。规则是实现 RD、GR、BL 三个函数,分别对应红、绿、蓝三原色通道,通过数学计算为每个像素点赋值。 文章的核心亮点在于展示了极简代码如何创造出令人惊叹的视觉艺术。例如,Martin Büttner 的作品仅用寥寥几个三角函数就生成了绚丽的渐变图案;而排名第一的作品更是利用随机数和递归,在极短的代码内实现了类似分形的纹理效果。文章还列举了用极简代码绘制 Mandelbrot 分形集的实例,颠覆了人们对复杂图形生成代码量的认知。 这些参赛代码巧妙利用数学公式、随机种子甚至静态变量,在字符限制内实现了图像算法的核心逻辑。文章不仅分享了有趣的代码片段,更展现了在极限约束下编程的创造力和美学,对于理解图形学原理和代码压缩技巧都很有启发。

IT 累计浏览 4,000

Web 开发程序员谈网游服务器开发

这篇讲的是作者在参加一次网游开发团队交流后的思考。他敏锐地捕捉到,传统网游服务器开发因逻辑与存储高度绑定,往往忽视了动态扩展与容灾能力,而这些恰恰是Web开发领域的强项。 作者的核心观点是,网游服务器可以借鉴Web架构的“服务无状态化”原则。关键在于将服务拆分为“逻辑(指令)”和“存储(状态)”两部分。一旦逻辑层本身无状态,就能像典型的Web应用(如PHP + MySQL)一样,实现服务器的弹性增减。即使面对“用户切换服务器后状态丢失”这类网游特有疑问,通过分离设计和将存储层集群化,同样能构建出高可扩展、高容灾的系统。 这个视角为游戏后台架构提供了一条清晰的优化路径:用Web成熟的工程思想,去解耦游戏服务器的紧耦合状态。

IT 累计浏览 1,953

C++模板”>>”编译问题与词法消歧设计

这篇讲的是C++中模板尖括号引起的经典编译难题及其在编译器设计中的巧妙化解。作者从编译原理的词法与语法分析管道出发,点明了C++98中`map>`为何必须写成`map >`——因为词法分析器会将`>>`识别为一个右移运算符,导致后续语法分析失败。 文章核心对比了两种消歧思路:一是采用“无扫描器解析”,即跳过独立的词法分析,直接在语法层面处理字符流,但这破坏了经典编译器结构;二是C++11的务实方案——让词法分析器遇到难以判定的序列(如`>>`)时,直接拆分为两个基础token(即两个`>`)交给语法分析器,再由语法分析器根据上下文(如是否在模板参数列表中)决定其正确含义。作者通过具体代码示例展现了这一改变带来的兼容性变化,并引用了C++11规范的相关定义。 文章清晰揭示了工程实践中为解决理论模型与具体语法冲突所做的权衡:既保留了清晰的模块化结构,又通过层次间的协作化解了歧义,这种设计思路对理解复杂语言的编译器实现很有启发。

IT 累计浏览 2,513

大整数乘法

这篇讲的是大整数乘法的经典实现思路与代码实践。作者用一个整形链表来存储大整数的每一位,核心计算部分通过两层循环遍历乘数与被乘数,将每对数字的乘积累加到结果链表的对应位置上,并通过一个递归辅助函数处理进位。 实现的亮点在于对基本数据结构的运用:自定义的BigInt结构体包含了数值、指针和符号位,通过重载运算符(如乘法、输入输出流)让操作更直观。不过作者也坦诚地指出了代码的几处“非典型”设计:乘法运算返回指针而非对象,递归计算存在栈溢出风险,以及指针管理上不够严谨。这些反思本身也是很有价值的经验点。 从代码片段可以看到具体的实现细节,比如如何构建数字链表、处理进位和负号。整体上,这是一次从零开始构建大整数运算的扎实尝试,既展示了基本算法,也揭示了手动管理内存时需要面对的复杂性。

IT 累计浏览 3,596

Linux大棚版gtest官网教程译文

这篇译文系统介绍了谷歌C++测试框架(gtest)的入门教程。作者从“为何选择gtest”切入,阐明了它作为一款优秀测试框架的核心设计哲学:确保测试独立可重复、通过“test case”组织以清晰反映代码结构、支持跨平台以保持可迁移性,并能提供详尽的失败信息以便高效调试。文章强调,gtest能帮助开发者从繁琐工作中解脱,专注测试内容本身。 在介绍完这些关键特性后,译文逐步展开教程内容。它首先指导如何将gtest编译为库并链接到测试项目,随后深入讲解了“断言”这一基础概念,区分了致命与非致命失败,并引入了“测试用例”和“测试夹具”等组织测试的核心组件。译文表明,这套基于xUnit框架的体系,旨在让有JUnit等经验的开发者快速上手,也让新用户能迅速构建出结构清晰、高效可靠的测试程序。