C++
文章平均质量分 78
瑜陀
Sophomore, major in Computer Science.
展开
-
debugger(七):栈帧(backtrace)
通过读取帧指针(frame pointer)和返回地址来遍历整个调用栈,直到达到main函数为止。每次循环都会输出当前函数的栈帧信息,并更新帧指针和返回地址以跳转到下一个栈帧。原创 2024-06-11 20:37:04 · 178 阅读 · 0 评论 -
debugger(六):source level 断点
这个可以在所有的编译单元中搜索。对于每一个 **cu**,只需要判断:`(die.has(dwarf::DW_AT::name) && at_name(die) == name)`,如果条件成立,那么就可以获取 **line entry** 了,但是这个 **entry** 并不好用,这一行可能是函数名,我们需要跳过这个 **prologue** 或者没用的行。原创 2024-06-11 18:37:27 · 184 阅读 · 0 评论 -
debugger(五):source level stepping
返回地址实际上是在调用函数之前的步骤中由调用指令(如 call)隐式处理的,它会将下一条指令的地址(即函数 f() 的地址)压入栈中,作为返回时应该跳转的地址。这里就很清晰了,因为在这之后,紧接着入栈的是 rbp,接着是把 rsp 放入 rbp,当前 rbp 中值就是那时候的 rsp,也是新的栈帧。我们只要把 rbp+8,就能得到它了。原创 2024-06-11 18:00:33 · 892 阅读 · 0 评论 -
debugger(四):源代码
注意这个 pc 是一个 offset addr,传参的时候一定要转换。思路很简单,首先遍历所有的 cu,然后判断 cu 的 low_pc 和 high_pc,如果在这个 cu 符合,那么就通过 cu 拿到 cu.root。cu.root 是一个根 die,通过它可以遍历所有的 die。之后再判断 die的 tag 是不是一个函数,如果是且包含 pc,那么就是我们要找的函数。原创 2024-06-10 22:36:15 · 384 阅读 · 0 评论 -
debugger(三):dwarf 文件
在讨论 DWARF 格式的调试信息时,编译单元(Compilation Unit, CU)和行表(Line Table)是两个核心概念。这些信息极大地促进了源码级调试,使调试器能够有效地将执行的机器代码映射回源代码。原创 2024-06-10 21:46:30 · 1210 阅读 · 0 评论 -
debugger(二):读、写内存以及寄存器
可以看到非常成功,利用 memory write 成功打了一个“断点”。事实上,这个断点只是暂时的,它并没有被记录在 debug 信息系统中,不过这不重要,这只是在验证 memory write 的功能。原创 2024-06-10 20:40:27 · 571 阅读 · 0 评论 -
debugger(一):打断点的实现以及案例分析
编译后,我们要打断点进行测试,可以看到目前只能传入一个地址,这个地址还是 0x 开头的 16 进制地址,我们对于这个地址丝毫没有头绪,因为我们不知道 std::cerr原创 2024-05-25 23:32:01 · 889 阅读 · 0 评论 -
C++内存模型
CPU 体系都是基于栈来运行程序,栈中主要存放函数的局部变量、函数参数、返回地址等,栈空间一般由操作系统进行默认分配或者程序指定分配,栈空间在进程生存周期一直都存在,当进程退出时,操作系统才会对栈空间进行回收。原创 2024-05-20 13:01:08 · 893 阅读 · 0 评论 -
C++:再谈智能指针
通过使用 std::make_unique,确保每次内存分配和对象构造都立即被 unique_ptr 接管。如果构造函数抛出异常,unique_ptr 从未被创建,但因为异常是在 make_unique 内部抛出的,已分配的内存会在抛出异常前由内部机制(通常是函数的栈展开)释放,这就避免了内存泄漏。原创 2024-05-18 22:22:57 · 1309 阅读 · 0 评论 -
C++调试:函数调用过程和堆栈
最后两条指令是函数结尾处的序曲,通常称作函数的 epilogue(尾声),它们的主要作用是恢复函数开始时保存的状态,并释放栈空间,以便函数可以正确地返回到调用者。原创 2024-05-17 16:43:00 · 966 阅读 · 0 评论 -
C++调试:内存管理
简而言之,S 和 B 都涉及未初始化的数据,但它们代表的是不同的内存区段。S 不属于 B,但两者都用于未初始化数据的存储,只是大小和存储优化方面有所区别。因此,在处理大型和小型未初始化数据时,编译器和链接器可能会选择将它们分别放在 .bss 或 .sbss 段中。原创 2024-05-16 22:44:48 · 289 阅读 · 0 评论 -
C++并发:构建线程安全的队列
在 threadsafe_queue 的拷贝构造函数中,尽管传入的 other 对象是一个 const 引用,我们仍然需要从这个 const 对象中复制数据。拷贝构造函数需要访问 other 对象的 data_queue,而为了线程安全,必须先锁定 other 的互斥量。由于 mut 是 mutable 的,即使在 const 上下文中,也能执行锁定操作。原创 2024-05-15 17:44:36 · 519 阅读 · 0 评论 -
C++并发:等待条件成立
故此,若判定函数有副作用,则不建议选取它来查验条件。 倘若真的要这么做,就有可能多次产生副作用,所以必须准备好应对方法。譬如,每次被调用时,判定函数就顺带提高所属线程的优先级,该提升动作即产生的副作用。结果,多次伪唤醒可“意外地”令线程优先级变得非常高。原创 2024-05-15 16:10:02 · 848 阅读 · 0 评论 -
C++:并发保护
std::call_once() 保证无论多少线程尝试调用指定的可调用对象,该对象的调用只会执行一次。它通过 std::once_flag 来控制,这个标志协调不同线程对函数调用的访问,确保目标函数只执行一次。原创 2024-05-14 21:57:45 · 1031 阅读 · 0 评论 -
C++并发:锁
在C++中,std::lock 是一个用于一次性锁定两个或多个互斥量(mutexes)的函数,而且还保证不会发生死锁。这是通过采用一种称为“死锁避免算法”的技术来实现的,该技术能够保证多个互斥量按照一定的顺序加锁。原创 2024-05-14 17:38:26 · 1021 阅读 · 0 评论 -
C++:完美转发(二)(std::forward)
C++中常量一般是进入符号表的,只有对其取地址时才会实际分配内存。 调用 f 函数时,其实参是直接从符号表中取值,此时不会发生问题。但当调用 fwd 时由于其形参是万能引用,而引用本质上是一个可解引用的指针。 因此当传入 fwd 时会要求准备某块内存以供解引用出该变量出来。但因其未定义,也就没有实际的内存空间, 编译时可能失败(取决于编译器和链接器的实现)。原创 2024-05-12 23:30:46 · 1081 阅读 · 0 评论 -
C++:完美转发(一)(std::forward)
std::forward 函数模板的实现,这是完美转发的关键机制。完美转发是指在模板函数中将参数维持原样(保持其值类别——左值或右值)传递给另一个函数的技术。std::forward 通常在实现需要将参数转发到其他函数的模板中使用,尤其是在构造函数、函数模板和其他接受任意参数的场景中。原创 2024-05-12 20:54:02 · 1721 阅读 · 0 评论 -
C++:移动语义(std::move)
移动语义允许资源(如动态内存)在对象间转移,而非复制,这可以显著提高性能,尤其是对于大型对象。移动构造函数和移动赋值操作通常应声明为noexcept以确保在资源转移过程中不抛出异常,这是因为异常可能导致资源泄露或其他问题。实现移动操作时,应确保正确处理自赋值的情况和资源的安全释放。原创 2024-05-11 23:43:16 · 1230 阅读 · 0 评论 -
C++:左值(引用)&右值(引用)
需要注意的是,func() 中的 b 其实是一个左值,换句话说,右值引用如果绑定了一个右值,它会延长这个右值的生命周期。 这种生命周期的延长意味着,尽管原始表达式产生的值是一个右值,一旦它被一个右值引用所绑定,它就不再是一个"即将销毁的临时值",而更像是一个普通的变量。 这允许开发者在保证效率的同时,也能够更灵活地控制这些值。原创 2024-05-10 22:09:59 · 1059 阅读 · 0 评论 -
C++并发:线程函数传参(二)
当向线程函数传参时,可能发生隐式类型转换,这种转换是在子线程中进行的。需要注意,由于隐式转换会构造临时对象,并将该对象(是个右值)传入线程函数,因此线程函数的形参应该是可接受右值类型的T、const T&或T&&类型,但不能是T&类型。此外,如果源类型是指针或引用类型时,还要防止可能发生悬空指针和悬空引用的现象。原创 2024-05-10 00:10:13 · 419 阅读 · 0 评论 -
C++并发:线程函数传参(一)
遇到的错误与尝试在 std::thread 构造器中使用不匹配的参数类型有关。错误的根本原因是在创建 std::thread 实例时传递的参数类型与线程函数所期望的参数类型不兼容。因此我们要考虑,传递的参数如何才能正确兼容线程函数期望的参数类型。这正是上面讨论到的一种情况,接着讨论如何优雅的解决这种问题。原创 2024-05-09 23:39:44 · 567 阅读 · 0 评论 -
C++:函数符(一)
相比之下,我们可以使用函数对象或者 lambda 表达式作为 std::for_each 的第三个参数,因为它们都是可调用对象。函数对象是一个类,重载了 operator() 运算符,可以像函数一样被调用。Lambda 表达式也是一个可调用对象,可以在需要时直接定义并使用。原创 2024-04-24 22:00:35 · 459 阅读 · 0 评论 -
C++:适配器
这些适配器类模板使得在不同场景下可以方便地使用不同的容器实现,同时提供了统一的接口,使得代码更加灵活和可复用。通过使用 STL 提供的适配器,可以更加方便地构造具有特定功能的容器,满足不同的需求。因此,stack、queue、priority_queue不被称为容器, 把它称为容器配接器。原创 2024-04-24 21:30:19 · 411 阅读 · 0 评论 -
C++:迭代器
需要注意的是,各种迭代器的类型并不是确定的,而只是一种概念性描述。 正如前面指出的,每个容器类都定义了一个类级 typedef 名称——iterator,因此 vector 类的迭代器类型为vector ::interator。然而,该类的文档将指出,矢量迭代器是随机访问迭代器,它允许使用基于任何迭代器类型的算法,因为随机访问迭代器具有所有迭代器的功能。原创 2024-04-23 21:45:57 · 642 阅读 · 0 评论 -
C++:智能指针(二)
这些智能指针的选择取决于所需的所有权模型和内存管理策略。使用适当的智能指针可以大大简化代码,提高程序的可维护性和可靠性。原创 2024-04-21 23:52:29 · 770 阅读 · 0 评论 -
C++:智能指针(一)
文章展示了如何正确地使用unique_ptr对象作为容器元素,并对show()函数的参数传递方式进行了解释,强调了按引用传递参数的重要性,以避免不必要的复制操作和编译错误。原创 2024-04-21 23:18:59 · 447 阅读 · 0 评论 -
C++:返回值优化
这种优化称为返回值优化(Return Value Optimization,简称 RVO),它能够提高程序的性能并减少不必要的对象拷贝。在这种情况下,编译器将对象直接构造在函数调用方指定的内存空间中,而不是通过复制构造函数创建临时对象。原创 2024-04-17 19:25:57 · 457 阅读 · 0 评论 -
C++:异常处理
可以看到catch()的参数尽管可以被设为引用,但是其实仍然要发生异常对象的复制(用复制构造函数)。如果没有被设置为引用,那么在最后,不仅要析构异常对象副本,还要析构异常对象本身。原创 2024-04-17 18:01:19 · 378 阅读 · 0 评论 -
C++:弃值表达式
弃值表达式是一种编程技巧,用于清除不必要的警告或明确表示我们不关心某些表达式的返回值,而只关注它的副作用。原创 2024-04-11 20:41:12 · 378 阅读 · 0 评论