5

我正在使用 std::thread 执行多个线程。我将指向数组的指针作为参数传递,类似于:

my_type* rest[count];
//Fill rest array
std::thread(fnc, rest, count);

我似乎遇到的问题是,在某个地方,“rest”中的指针值被破坏了。我在调用 std::thread 之前打印出指针值,以及 std::thread 代表我调用的函数中的第一件事,并且值不匹配。它看起来相当随机,有时它们会匹配,有时不会(当后者发生时会导致段错误)。

我知道(从我能在该主题上找到的一点点)std::thread 复制了参数,我认为我的问题源于此,并且有一个特殊的函数 std::ref() 允许它传递引用,但没有一个特别提到指针。我尝试了各种技术来尝试使用 std::ref() 传递这个数组,但我还没有解决这个问题。

我认为这可能是我的问题的原因是正确的,还是我在吠叫错误的树?

4

2 回答 2

7

如果以某种方式转换(数组指针,而不是内容),那么我会有问题。

是的,这正是发生的事情。

人们经常错误地认为数组只是指针。事情的真相是,每当你声明一个接受数组的函数时:

void foo(int x[10]);

声明被“调整”,因此参数是一个指针:

void foo(int *x); // C++ can't tell the difference between this and the first declaration

当您调用该函数时:

int x[10];
foo(x);

有一个等效于以下内容的隐式转换:

int x[10];

int *tmp = &x[0];

foo(tmp);

所以发生的事情是你有一块内存,其中包含指向长寿命对象的指针:

my_type *rest[count] = {new my_type, new my_type, new my_type};

您将指向该内存块的指针传递给线程:

thread(fnc, &rest[0], count);

然后当函数返回rest超出范围时,该内存块不再有效。

然后线程跟随指向内存块的指针并读取垃圾。如果它确实读取了正确的数组内容,那么它可以很好地访问长寿命的对象。rest问题是从曾经在堆栈上的损坏的内存块中获取指向长寿命对象的指针。

有没有办法抑制这种行为?

在大多数情况下,唯一有意义的就是不使用原始数组作为函数参数。您可以将原始数组包装在结构中并获得合理的行为:

struct int_array {
  int x[10];
};

void foo(int_array x);

int main() {
  int_array x = {1,2,3,4,5,6,7,8,9,0};
  foo(x); // the array is copied rather than getting strangely converted
}

这几乎就是std::array它的作用,所以你最好使用它。

如果您不想要数组的副本,您可以引用该数组:

int foo(int (&x)[10]);

这为您提供了与在您背后使用int foo(int x[10]); foo(x);. 这里的好处是它是明确的,并且您可以对数组的大小进行类型检查。也就是说,由于“调整”,以下不会导致编译器错误:

int foo(int x[10]);

int x[3];
foo(x);

而这将:

int foo(int (&x)[10]);

int x[3];
foo(x); // the implicit conversion to &x[0] does not get happen when the function takes a reference to array
于 2013-10-04T02:38:08.550 回答
0

为了让您意识到代码的风险,请尝试执行以下操作:

#include <thread>
#include <iostream>

void f() { std::cout << "hello" << std::endl; }

int main()
{
    {
        auto t = std::thread(f);
        std::cout << "0" << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
        std::cout << "1" << std::endl;
    }
    std::cout << "2" << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(400));
    std::cout << "3" << std::endl;
}

您将看到2并且3永远不会输出,因为应用程序已终止。

事实上,它更微妙,因为在我的示例中,我已将线程移至t. 类似于您的原始样本并且没有将线程分配给任何变量,没有提前终止,但"hello"永远不会输出。(可能有一个优化来消除临时的,因为它从未使用过;它只是在加入之前被破坏了;或者谁知道......)

于 2013-10-04T03:09:17.300 回答