本文用作日常随手记,平时遇到一些琐碎的技术细节就随便扔进来备查。必要时也可整理成独立博文发表。
_CrtSetBreakAlloc
定位内存泄漏点现象:Visual Studio 调试运行程序结束后在输出栏提示有内存泄漏,内容如下
Detected memory leaks!
Dumping objects ->
{5481} normal block at 0x01371820, 11648 bytes long.
Data: < > 0C F2 B7 00 0A 00 00 00 CD CD CD CD CD CD CD CD
Object dump complete.
然而它并未具体指出泄漏点在哪里,很难复查。我看到这里直接麻爪了。
解决:在 main 函数开始处添加一行代码 _CrtSetBreakAlloc(5481);
,再次调试运行,IDE会自动在该内存块被申请时中断下来,程序员通过调用堆栈(CallStack)可以很容易的定位内存泄漏点。看到这个结果我当时很惊喜。
注意,调用 _CrtSetBreakAlloc 时传入的参数就是之前IDE报告的内存泄漏序号:
{5481} normal block at 0x01371820, 11648 bytes long.
_CrtSetBreakAlloc(5481);
原理:在调试模式下,malloc/free等函数调用被替换品取代,记录每一次内存分配和释放动作,并为新分配的内存块顺序编号(从1到N),到程序退出时将未被释放的内存块编号打印出来。后来代码加上 _CrtSetBreakAlloc(N) 调用再次编译运行并不影响内存分配释放顺序,只要程序执行流程不变,本轮第N次内存分配跟上次运行的第N次内存分配是同一个调用点。
给Visaul Studio的建议:其实可以事先记录内存分配调用点的文件名和行号并在检测到泄漏时第一时间报告出来,同时提示用户借助 _CrtSetBreakAlloc 追踪调用栈。
2018-11-29, by Liigo.
ziglang.org
defer
语句。表明它没有自动化内存管理机制,也没有RAII。Gravity script
Wren script
WrenBindForeignMethodFn
是被引擎运行时按需调用的,需根据文本识别类名和函数签名再返回相应函数地址,每次只绑定一个外部函数,且无法一次性实现批量绑定。作者认为设计如此无需更改。 ISSUE 648Jai language
Jai will NOT have:
Smart pointers
Garbage collection
Automatic memory management of any kind
Templates or template metaprogramming
RAII
Subtype polymorphism
Exceptions
References
A virtual machine (at least, not usually—see below)
A preprocessor (at least, not one resembling C’s—see below)
Header files
see https://github.com/BSVino/JaiPrimer/blob/master/JaiPrimer.md
2019-1-3, by Liigo.
Mun
AOT+静态类型,热加载,LLVM编译,GC,高性能,使用Rust开发,采用Rust语法。执着沉醉于热加载究竟有利有害?我是GET不到它的点。
Our philosophy is to only add new language constructs when those can be hot reloaded.
V
看历史是2019年后半年开始开发,到现在还不到一年,居然已经羽翼丰满,周边生态也颇为活跃,刮目相看。值得上手并持续关注!
Simple, fast, safe, compiled language for developing maintainable software. Compiles itself in <1s with zero library dependencies.
V语言Windows版编译时依赖MSVC,在这一点比不上Zig。Zig内置Clang开箱即用,发行包也只有50MB左右。V的发行包虽然仅有2MB,但架不住MSVC大呀(几个GB都打不住)。
builder error: Cannot find MSVC on this OS
除此之外,V照搬了很多Go语言的设计,包括一些糟糕的设计(或者说我不喜欢的)。对Go语言的修正当然也有,不多。到处在复制别人的东西,创新不足,无突出亮点。
Zig
上个月刚发布了0.6版本。已经完成自举的、明确喊出要取代C语言的Zig语言,目前受到有许多人推崇,其前景究竟几何?我实在看不惯它大量以@
开头的builtin函数,尤其是那个@import("std")
。
上周踩了sscanf的坑,相关伪代码如下:
#include
#include
int main() {
unsigned int canid;
int t,dlc;
unsigned char data[8];
char linebuf[128];
snprintf(linebuf, sizeof(linebuf), "%d|%08X|%d|%02X %02X %02X %02X %02X %02X %02X %02X\n",
t, canid, dlc, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]);
sscanf(linebuf, "%d|%08X|%d|%02X %02X %02X %02X %02X %02X %02X %02X\n",
&t, &canid, &dlc, &data[0], &data[1], &data[2], &data[3], &data[4], &data[5], &data[6], &data[7]);
sscanf(linebuf, "%d|%x|%d|%hhx %hhx %hhx %hhx %hhx %hhx %hhx %hhx\n",
&t, &canid, &dlc, &data[0], &data[1], &data[2], &data[3], &data[4], &data[5], &data[6], &data[7]);
}
真实代码由两部分组成,一部分是用snprintf
写出格式化文本,另一部分是用sscanf
解析格式化文本,中间是完全可控的内部临时文件。我想当然的在snprintf
和sscanf
中使用了相同的格式文本,其中隐藏了隐晦的错误。关键点在于,格式符%x
的操作对象是unsigned int
类型,而data[0]
等的类型是unsigned char
。data[0]
传入snprintf
时被自动扩展类型,并不会导致错误。而&data[0]
传入sscanf
时,实际传入的类型是unsigned char*
,而sscanf
期望的类型是unsigned int*
(基于%x
);程序员的本意是写出一个字节,而程序实际写出4个字节(x86平台)。例如,sscanf
向&data[7]
写出4个字节将导致越界访问data数组,进而改写附近的其他局部变量值(以本例来说是dlc
被改写)。更加隐晦的是,虽然逻辑错误很明显,而程序实际运行结果却“基本正确”,并没有表现出明显的错误结果让程序员立刻意识到错误(写内存越界后可能要过一段时间再制造不良后果)。
最终将sscanf
的格式符改为%hhx
才是正确的,它对应的操作对象类型是unsigned char*
,与&data[0]
等是一致的。
2019-07-16, by Liigo.