简介

  • C/C++程序编译或者运行常见错误

windows下C++程序运行出错: Access violation reading location

  • 在Visual Studio 2019的C++工程,编译是没有问题的,但是在程序调试运行时经常会出现’Access violation reading location’的问题,经过调试跟踪后,发现是由于变量被多个代码段访问到,可能出现了空指针的情况。这种错误也不是每次都发生,所以比较难找到原因和追踪到。具体原因可能是所访问的变量已经被其它部分代码改动了,具体建议如下: Then verify that the values are not being unintentionally changed somewhere in the program by creating a Data Breakpoint for the pointer in question to make sure it isn’t being modified elsewhere in the program.

  • 使用以下两个办法可以大大减少此类问题的发生。

    • 在有可能发生变量使用冲突的地方加上 try {…} catch (std::exception ex) {} 来捕捉这个异常,异常被捕捉到后程序时可以接着运行的
    • 减少多段代码访问同一个变量的情况。

warning C4819: 该文件包含不能在当前代码页(936)中表示的字符。请将该文件保存为 Unicode 格式以防止数据丢失

  • 使用vs编译时遇到这个错误,正常情况下写的文件是没有问题的,只要用其它编辑器修改过后再保存,再用vs编译就会出现这个问题。
  • 解决方法
    • 在VS IDE中将文件另存为,在保存中选择高级保存选项,选择 Unicode (UTF-8 带签名) - 代码页65001

C++ to ‘const std::mutex’ discards qualifiers 错误

这个错误通常是因为你在尝试将一个非 const 的 std::mutex 对象赋值给一个 const std::mutex 对象,或者你在一个 const 成员函数中尝试对一个非 const 的 std::mutex 对象进行操作。这违反了 C++ 的 const-correctness 原则,即 const 对象只能调用 const 成员函数,并且不能被修改。

以下是一个示例,展示了可能导致这个错误的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <mutex>

class MyClass {
public:
    void doSomething() const {
        // 错误!const 成员函数中尝试对非 const 的 mutex 进行操作
        mutex.lock();
        std::cout << "Doing something..." << std::endl;
        mutex.unlock();
    }

private:
    mutable std::mutex mutex; // 使用 mutable 关键字标记 mutex 可以在 const 成员函数中修改
};

int main() {
    const MyClass obj;
    obj.doSomething();
    return 0;
}

要解决这个问题,你需要确保在 const 成员函数中只能对 std::mutex 对象调用 const 成员函数,或者使用 mutable 关键字标记 std::mutex,以便在 const 成员函数中修改它。

C++ as ‘this’ argument discards qualifiers [-fpermissive] 错误

这个错误通常是由于在常量成员函数中试图修改成员变量引起的。C++ 中的常量成员函数是指在函数声明和定义中使用 const 修饰的成员函数,表示该函数不会修改调用对象的成员变量。

错误信息 “‘this’ argument discards qualifiers” 意味着你在常量成员函数中试图修改 this 指针所指向的对象。这是不允许的,因为常量成员函数被设计为不修改对象的状态。

以下是一个简单的例子,展示了可能导致这个错误的情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>

class MyClass {
public:
    int data;

    // 常量成员函数,试图修改成员变量
    void modifyData() const {
        // 错误:试图在常量成员函数中修改成员变量
        // data = 42;
        std::cout << "Data: " << data << std::endl;
    }
};

int main() {
    const MyClass obj;
    obj.modifyData();  // 调用常量成员函数

    return 0;
}

在上述示例中,modifyData 是一个常量成员函数,但它试图修改 data 成员变量,这将导致上述错误。

要解决这个问题,你应该确保在常量成员函数中不修改成员变量。如果你需要在常量对象上调用一个能够修改成员变量的函数,该函数应该被声明为非常量成员函数。如果你确实需要在常量对象上进行某些修改,你可能需要使用 mutable 关键字来声明一个可变的成员变量。

示例:

1
2
3
4
5
6
7
8
9
class MyClass {
public:
    mutable int data; // 使用 mutable 关键字声明可变成员变量

    void modifyData() const {
        data = 42; // 合法:在常量成员函数中修改可变成员变量
        std::cout << "Data: " << data << std::endl;
    }
};

请根据你的需求选择适当的解决方案。

C++ 编译错误 error: prototype for

看起来你遇到了一个编译错误,错误信息中包含了 “error: prototype for”。这通常是因为函数的声明和定义之间存在不一致,导致编译器无法匹配它们。

常见的情况包括:

  1. 函数声明和函数定义之间的参数不匹配。
  2. 函数声明和函数定义之间的返回类型不匹配。

要解决这个问题,你需要确保函数的声明和定义在参数和返回类型等方面保持一致。以下是一些可能导致这个错误的情况以及解决方法:

情况1:参数不匹配

假设你有以下的函数声明和定义:

1
2
3
4
5
6
7
// 声明
void myFunction(int x, int y);

// 定义
void myFunction(int a, int b) {
    // 函数体
}

在这个例子中,声明和定义中的参数名称不一致(x、y 和 a、b)。编译器会认为这是两个不同的函数,因此会产生错误。解决方法是保持参数名称一致:

1
2
3
4
// 声明和定义都使用相同的参数名称
void myFunction(int x, int y) {
    // 函数体
}

情况2:返回类型不匹配

另一个可能的情况是函数的声明和定义之间的返回类型不匹配:

1
2
3
4
5
6
7
// 声明
int myFunction();

// 定义
void myFunction() {
    // 函数体
}

在这个例子中,声明指定了函数返回类型为 int,但定义实际上是一个无返回值的函数(返回类型为 void)。要解决这个问题,保持返回类型一致:

1
2
3
4
// 声明和定义都使用相同的返回类型
int myFunction() {
    // 函数体
}

请检查你的代码,确保函数的声明和定义在参数和返回类型等方面保持一致,这样编译器就不会报 “error: prototype for” 错误了。如果问题仍然存在,你可以提供更多的代码片段和错误信息,我将尽力帮助你找到解决方案。

C++ 编译错误 error: candidate is

你好!你提供的信息有点有限,我需要更多的上下文来帮助你解决这个问题。”error: candidate is” 是一个编译错误的提示信息,通常出现在 C++ 代码中。这个错误提示通常是由于在某个上下文中找到了多个可能匹配的候选项,编译器无法确定要选择哪个。

为了更好地帮助你,可以提供以下信息:

  1. 完整的错误提示信息,包括错误的文件路径、行号和列号。
  2. 引发错误的代码片段,尤其是错误所在的代码行附近的代码。
  3. 你正在尝试进行的操作,以及你的预期结果。

这些信息将有助于我更准确地理解问题,并为你提供更具体的帮助。

没有命名类型: declare class does not name type

  • 出现这个编译错误主要有四个可能原因,现总结如下:
    • 引用的类命名空间未包含
    • 引用的类头文件未包含
    • 包含了头文件,或者已经前置声明了,则说明所引用的类名写错了
    • 循环引用头文件
    • 定义或声明的语句,第一个词一定是类型
  • 前置声明要素:
    • 前置声明需要注意以上提到的几点
    • 尽可能的采用前置声明,做到只有包含继承类的头文件
    • 使用前置声明时,CPP文件中include头文件次序必须先包含前置声明的类定义头文件,再包含本类头文件。否则会出现错误:
      • (expected constructor, destructor, or type conversion before ‘typedef')
  • 前置声明
    • XXX应该是一种用户定义的数据类型,而由于没有声明或者拼写错误或者与关键词重名,导致编译有错,出现类型错误
    • 在一个源文件中,要声明或定义一个类的指针时,必须在使用前声明或定义该类
  • 使用前置声明:
    • class Base;

未声明的引用

  • 如果错误是未声明的引用,那就是找不到函数的原型

  • 解决办法,通常是相关的头文件未包含

未定义引用:undefined reference to 'xxx'

  • 未定义引用的问题是:编译之后,链接阶段出现的

  • 未定义引用产生的原因:
    • 一个是,生成库文件的时候,某个源文件(cpp)路径不正确
    • 另一个是,有函数声明,但是没有函数定义
  • 出现未定义引用的错误原因,主要是C/C++编译为object文件的时候,并不需要函数的具体实现,只要有函数的原型即可,但是在链接可执行文件的时候就必须要具体的实现了。

  • 一般的原因:
    • gcc依赖顺序问题
    • 库文件找不到
    • 库文件损坏
    • C++编译时会修改函数名,CPP文件引用C文件中的函数,混编时很可能报未定义的错误,需要使用extern "C"包裹起来

段错误:Segmentation fault

  • 段错误是什么?
    • 段错误是指访问的内存超出了系统给这个程序所设定的内存空间。
  • 段错误产生的原因
    1. 访问不存在的内存地址
    2. 访问系统保护的内存地址
    3. 访问制度的内存地址
    4. 栈溢出
    5. delete使用错误

corrupted size vs. prev_size 问题 内存越界

  • 在C++中我们时常会遇见corrupted size vs. prev_size的报错,它的原因在于内存越界
  • 其实解决这个问题的办法在于使用vector来代替自己申请内存,并且使用以下两种方法来访问元素:
    • 使用迭代器
    • 使用at
  • 尽量要避免使用[]来访问vector,越界的时候很难找到错误的地方,使用at带有越界的检查,更为安全。

memory corruption 内存泄漏

内存问题

  • 内存问题始终是C++程序员需要去面对的问题,这也是C++语言门槛高的原因之一

  • 常见的内存问题:
    1. 内存重复释放,出现double free时,通常是由于这种情况导致的
    2. 内存泄漏,分配的内存忘记释放
    3. 内存越界使用,使用了不该使用的内存
    4. 使用了无效指针
    5. 空指针,对一个空指针进行操作
  • 内存越界,引起的问题有极大的不确定性,有时大,有时小,有时可能不会对程序的运行产生影响,正是这种不易复现的错误,才是最致命的。
  • 内存越界,通常可能会造成如下几种情况:
    1. 破坏了堆中的内存分配信息数据,特别是动态分配的内存块的内存信息数据。因为操作系统在分配和释放内存块时需要访问该数据,一旦该数据被破坏,可能会出现一下几种情况:
      1
      2
      3
      4
      
         *** glibcdetected *** free(): invalid pointer:
         *** glibcdetected *** malloc(): memory corruption:
         *** glibcdetected *** double free or corruption (out): 0x00000000005c18a0 ***
         *** glibcdetected *** corrupted double-linked list: 0x00000000005ab150***
      
    2. 破坏了程序自己的其他对象的内存空间,这种破坏会影响程序执行的不正确性,当然也会诱发coredump,如破坏了指针数据
    3. 破坏了空闲内存块
    4. 通常,代码错误被激发是偶然的,也就是说之前的程序一切正常,可能由于为类增加了两个成员变量,或者改变了某一部分代码,coredump就频繁发生,而增加的代码绝不会有任何问题,这时应该考虑是否是某些内存被破坏了
  • 注意事项:
    1. 出现段错误时,首先应该想到段错误的定义,从它出发考虑引发错误的原因
    2. 在使用指针时,定义了指针后记得初始化指针,在使用的时候记得判断是否为NULL
    3. 在使用数组时,注意数组是否被初始化,数组下标是否越界,数组元素是否存在等
    4. 在访问变量时,注意变量所占地址空间是否已经被程序释放
    5. 在处理变量时,注意变量的格式控制是否合理等

核心转储:core dumped

  • 当程序运行的过程中异常终止或崩溃,操作系统会将程序当时的内存状态记录下来,保存在一个文件中,这种行为就叫做Core Dump(中文有的翻译成 核心转储)
  • 可以认为core dump是内存快照,但实际上,除了内存信息之外,还有一些关键的程序运行状态也会同时dump下来,例如寄存器信息(包括程序指针,栈指针),内存管理信息,其他处理器和操作系统状态和信息。
  • Segmentation fault (core dumped)通常是内存未正常操作造成。空指针,野指针的读写操作,数组越界访问,破坏常量等。

  • 打开core dump功能
    1. 在终端输入命令ulimit -c,输出结果为0,表示关闭core dump,即当程序异常终止时,也不会生成core dump文件
    2. 可以使用命令ulimit -c unlimited来开启core dump功能,并且不限制core dump文件的大小;如果需要限制文件的大小,将unlimited换成想生成文件的存储上限,单位为blocks(KB)
    3. 用上面命令只会对当前终端环境有效,如果想要永久生效,需要设置文件/etc/security/limits.conf文件
    4. 默认生成的core文件保存在可执行文件所在的目录下,文件名就为core
    5. 修改文件/proc/sys/kernel/core_uses_pid文件可以让生成core文件名自动加上pid号
    6. 修改文件/proc/sys/kernel/core_pattern控制生成core文件保存的位置以及文件名格式

Linux下C/C++程序内存泄露检查工具

  • valgrind:强大开源的程序检测工具
  • mtrace:GNU扩展,用来跟踪malloc
  • dmalloc:用于检查C/C++内存泄露的工具,即是检查是否在程序运行结束还没有释放的内存,以一个运行库发布
  • memwatch:和dmalloc一样,能够检测未释放的内存,同一段内存被释放多次,位地址取错误及不当使用未分配的内存区域
  • mpatrol:一个跨平台的C++内存泄露检测器
  • dbgmem:是一个动态库发布的形式,有点类似于dmalloc

Valgrind详解

  • Valgrind包含以下一些工具:
    1. Memcheck:这是valgrind应用最广泛的工具,一个重量级的内存检查器,能够发现 开发中绝大不多述的内存错误使用的情况,比如:使用未初始化
    2. callgrind:主要用来检查程序中函数调用过程中出现的问题
    3. cachegrind:主要用来检查程序中缓存使用出现的问题
    4. Helgrind:主要用来检查多线程中出现的竞争问题
    5. Massif:主要用来检查程序中堆栈使用中出现的问题
    6. Extension:可以使用core提供的功能,自己编写特定的内存调试工具
  • 内存检查原理:
    1. Valid-value表:对于进程的整个地址空间中的每一个字节(byte),都有与之对应的8个bits,对于CPU的每个寄存器,也有一个与之对应的bit向量,这些bits负责记录该字节或者寄存器值是否具有有效的,已经初始化的值
    2. Valid-Address表:对于进程整个地址空间中的一个字节(byte),还有与之对应的1bit,负责记录改地址是否能够被读写
    3. 检测原理:
      • 当要读写内存中的某个字节时,首先检查这个字节对应的A bit, 如果该A bit显示该位置是无效位置,memcheck则报告读写错误
      • 内核(core)类似于一个虚拟的CPU环境,这样当内存中的某个字节被加载到真实的CPU中时,该字节对应的V bit也被加载到虚拟的CPU环境中,一旦寄存器中的值,被用来产生内存地址,或者该值能够影响程序的输出,则memcheck会检查对应的vbits,如果该值尚未初始化,则会报告使用未初始化内存错误
  • Valgrind的安装
    1. 解压安装包:tar -jxvf valgrind-3.11.0.tar.bz2 -C /usr/local/src
    2. 进入目录安装:cd /usr/local/src/valgrind-3.11.0
    3. 运行 ./autogen.sh 设置环境(需要标准的autoconf工具):./autogen.sh
    4. 配置Valgrind,生成Makefile文件:./configure --prefix=/usr/local
    5. 编译和安装valgrind:make && make install
  • Valgrind的使用
    • 为了valgrind发现的错误更精确,如能够定位到源代码的行,建议在编译时加上-g参数,编译优化选项选择O0(不要优化)
    • 利用valgrind调试内存问题,不需要重新编译源程序,它的输入就是二进制的可执行程序
    • 调用valgrind的通用格式:valgrind [valgrind-options] your-program [your-program-options]
    • Valgrind的参数分为两类
      • 一类是core的参数,它对所有的工具都适用
      • 另一类就是具体某个工具,如memcheck的参数.
    • Valgrind默认的工具就是memcheck,也可以通过 -tool=toolname 指定其他的工具
  • Memcheck将内存泄露分为两种:
    1. Possibly lost:可能的内存泄露
      • Possibly lost是指仍然存在某个指针能够访问某块内存,但是该指针指向的已经不是该内存的首地址
    2. Definitely lost:确定的内存泄漏
      • 确定的内存泄露是指已经不能够访问这块内存
      • Definitely lost又分为两种:
        1. direct:直接,直接是没有任何指针指向该内存
        2. indirect:间接,指向该内存的指针都位于内存泄露处
  • Valgrind常用命令
    • -log-file=valReport :指定生成分析日志文件到当前执行目录,文件名为 valReport
    • -leak-check=full :显示每个泄露的详细信息
    • -show-reachable=yes :是否检测控制范围之外的泄露,比如全局指针,static指针等,显示所有的内存泄露类型
    • -leak-resolution=low :内存泄露报告合并等级
  • Valgrind输出内容:
    • ==98725== :进程号,如果程序使用了多进程的方式来执行,就会显示多个进程的内容
    • 第一段是valgrind的基本信息
    • 第二段是对堆内存分配的总结信息,
    • 第三段的内容描述了内存泄露的具体信息
    • 最后一段是总结,4字节为一块的内存泄露

C++ 编译: crosses initialization of …

  • 在switch-case中定义变量,编译时出现异常:crosses initialization of……,
  • 异常代码如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    switch (ev)
    {
      case MG_EV_HTTP_MSG:
          std::thread tmp(EventHandler, connect, hm);
          tmp.detach();
          break;
      default:
          break;
    }
    
  • 出现异常的原因是:
    • C/C++中变量的生命周期问题,在case MG_EV_HTTP_MSG中定义了变量tmp,在default中也能够使用,但是如果在程序运行中直接跳入default分支,就会出现没有初始化的异常。
    • 程序编译时为了防止出现上述情况,就会报编译失败,不是证明程序有异常,只是编译器担心程序有异常。
  • 解决方法有两个
    • 在switch-case外进行定义:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      
      std::thread tmp(EventHandler, connect, hm);
      switch (ev)
      {
      case MG_EV_HTTP_MSG:
          tmp.detach();
          break;
      default:
          break;
      }
      
    • 在case中加花括号
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      
      switch (ev)
      {
      case MG_EV_HTTP_MSG:
      {
          std::thread tmp(EventHandler, connect, hm);
          tmp.detach();
          break;
      }
      default:
          break;
      }