C++多线程场景中的变量提前释放导致栈内存异常

简介: C++多线程场景中的变量提前释放导致栈内存异常

多线程场景中的栈内存异常

在子线程中尝试使用当前函数的资源,是非常危险的,但是C++支持这么做。因此C++这么做可能会造成栈内存异常。

正常代码

#include <iostream>
#include <thread>
#include <windows.h>
// 线程函数,用于执行具体的任务
void fun(int param)
{
  std::cout << __FUNCTION__ << " param:" << param << std::endl;
  Sleep(3000);
  param = 1;
  std::cout << __FUNCTION__ << " param:" << param << std::endl;
}
// 线程函数,用于执行具体的任务
void threadFunction() {
  // 在这里执行线程的具体任务
  int num = 2;
  //在函数中还启动了一个线程
  std::thread funThread([num]()
    {
      fun(num);
    });
  funThread.detach();
}
int main() {
  const int numThreads = 3;
  std::thread threads[numThreads];
  // 创建并启动多个线程
  for (int i = 0; i < numThreads; ++i) {
    threads[i] = std::thread(threadFunction);
  }
  //等待所有线程执行完毕
  for (int i = 0; i < numThreads; ++i) {
    threads[i].join();
  }
  Sleep(1000);
  std::cout << "All threads have completed." << std::endl;
  return 0;
}

上述是一个正常的多线程代码。

异常代码

但是如果将其中多线程传参设置为引用传递,可能就会造成栈内存异常了,如下所示:

#include <iostream>
#include <thread>
#include <windows.h>
// 线程函数,用于执行具体的任务
void fun(int& param)//传参修改为引用传递
{
  std::cout << __FUNCTION__ << " param:" << param << std::endl;
  Sleep(3000);
  param = 1;
  std::cout << __FUNCTION__ << " param:" << param << std::endl;
}
// 线程函数,用于执行具体的任务
void threadFunction() {
  // 在这里执行线程的具体任务
  int num = 2;
  //在函数中还启动了一个线程,传参为引用
  std::thread funThread([&num]()
    {
      fun(num);
    });
  funThread.detach();
}
int main() {
  const int numThreads = 3;
  std::thread threads[numThreads];
  // 创建并启动多个线程
  for (int i = 0; i < numThreads; ++i) {
    threads[i] = std::thread(threadFunction);
  }
  //等待所有线程执行完毕
  for (int i = 0; i < numThreads; ++i) {
    threads[i].join();
  }
  Sleep(1000);
  std::cout << "All threads have completed." << std::endl;
  return 0;
}

编译成功,但是运行失败。

运行结果:

上面std::thread funThread(&num{fun(num);});,采用引用传递,并且void fun(int& param)中也采用引用入参的形式。此时可能就会产生内存异常。

因为传入参数num是一个局部参数,我们在fun中修改或读取param时,可能num已经被释放掉了,这时候修改或者读取param就会发生内存错误,其实这里是栈内存异常。这是非常危险的,因为栈内存可能会造成无法估量的问题。本代码中只有这一个作业线程,所以就在这里报错了,所以就在任务函数中崩掉了。但是因为代码中可能还可以读取修改参数param,而导致其他线程中的异常,这种问题往往很难定位。

优化代码

当然我们用引用传递的好处是可以避免临时变量的产生,但变量是复杂对象时,还是可以很大程度减少内存消耗。虽然不能用引用,但是我们可以使用std::move来实现变量所有权的转移,也可以减少临时变量的拷贝。

#include <iostream>
#include <thread>
#include <windows.h>
// 线程函数,用于执行具体的任务
void fun(int&& param)//右值传参
{
  std::cout << __FUNCTION__ << " param:" << param << std::endl;
  Sleep(3000);
  param = 1;
  std::cout << __FUNCTION__ << " param:" << param << std::endl;
}
// 线程函数,用于执行具体的任务
void threadFunction() {
  // 在这里执行线程的具体任务
  int num = 2;
  //在函数中还启动了一个线程
  std::thread funThread(fun, std::move(num));//使用move传入右值
  funThread.detach();
}
int main() {
  const int numThreads = 3;
  std::thread threads[numThreads];
  // 创建并启动多个线程
  for (int i = 0; i < numThreads; ++i) {
    threads[i] = std::thread(threadFunction);
  }
  //等待所有线程执行完毕
  for (int i = 0; i < numThreads; ++i) {
    threads[i].join();
  }
  Sleep(1000);
  std::cout << "All threads have completed." << std::endl;
  return 0;
}


目录
相关文章
|
11天前
|
程序员 编译器 C++
【C++核心】C++内存分区模型分析
这篇文章详细解释了C++程序执行时内存的四个区域:代码区、全局区、栈区和堆区,以及如何在这些区域中分配和释放内存。
27 2
|
6天前
|
安全 C++
超级好用的C++实用库之环形内存池
超级好用的C++实用库之环形内存池
23 5
|
6天前
|
C++
超级好用的C++实用库之动态内存池
超级好用的C++实用库之动态内存池
15 0
|
2月前
|
存储 安全 C++
C++:指针引用普通变量适用场景
指针和引用都是C++提供的强大工具,它们在不同的场景下发挥着不可或缺的作用。了解两者的特点及适用场景,可以帮助开发者编写出更加高效、可读性更强的代码。在实际开发中,合理选择使用指针或引用是提高编程技巧的关键。
24 1
|
22天前
|
JavaScript 前端开发 Java
通过Gtest访问C++静态、私有、保护变量和方法
通过Gtest访问C++静态、私有、保护变量和方法
20 0
|
1月前
|
C语言 C++
C++(二)内存管理
本文档详细介绍了C++中的内存管理机制,特别是`new`和`delete`关键字的使用方法。首先通过示例代码展示了如何使用`new`和`delete`进行单个变量和数组的内存分配与释放。接着讨论了内存申请失败时的处理方式,包括直接抛出异常、使用`try/catch`捕获异常、设置`set_new_handler`函数以及不抛出异常的处理方式。通过这些方法,可以有效避免内存泄漏和多重释放的问题。
|
1月前
|
Java C++
Java内存区域于内存溢出异常
这篇文章详细解释了Java虚拟机的内存区域划分、各区域的作用以及可能遇到的内存溢出异常情况。
36 0
|
2月前
|
存储 程序员 编译器
c++学习笔记08 内存分区、new和delete的用法
C++内存管理的学习笔记08,介绍了内存分区的概念,包括代码区、全局区、堆区和栈区,以及如何在堆区使用`new`和`delete`进行内存分配和释放。
40 0
|
17天前
|
编译器 C++
C++ 类构造函数初始化列表
构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式。
60 30
|
6天前
|
并行计算 Unix Linux
超级好用的C++实用库之线程基类
超级好用的C++实用库之线程基类
12 4