常量初始化程序是否不保证同步?

huangapple go评论96阅读模式
英文:

Is const initializer not guaranteed synchronous?

问题

如果我使用g++ -O3编译(其他优化标志正常工作),构造函数初始化器在printf()之前并不总是初始化变量(至少对我来说是这样看的)。我猜想这可能是编译器以某种方式并行化了,因为如果我插入或删除标记的行,结果一直是好的。如果这段代码在你的机器上正常工作,请尝试多次运行它(使用-O3g++.exe (Rev10, Built by MSYS2 project) 12.2.0)。

英文:

If I compile with g++ -O3 (other optimization flags work fine) the constructor initializer does not always init the variable before the printf()(at least it looks like this for me). My guess is that it's getting somehow parallelized by the compiler, because if I insert or remove marked lines I am getting consistently good results. If the code is working for you, try running it multiple times(-O3 and g++.exe (Rev10, Built by MSYS2 project) 12.2.0).

As requested here is the original code:

#include <vector>
#include <iostream>

struct dataset
{
    struct sample
    {
        float* const x;
        float* const y;

        sample(const int inputSize, const int outputSize) : x(new float[inputSize]), y(new float[outputSize])
        {
        }
        sample(float* const inputs, float* const outputs) : x(inputs), y(outputs)
        {
        }

        void clear()
        {
            delete[] x;
            delete[] y;
        }
    };

private:
    sample* samples = nullptr;
    int sampleCount = 0;
    const bool manageMemory;

public:
    const int xsize;
    const int ysize;

    dataset(const int inputSize, const int outputSize, const int reserved)
        : xsize(inputSize), ysize(outputSize), manageMemory(true)
    {
        // printf("%i == %i\n", xsize, inputSize); // <-- when I insert this line it works fine
        reserve(reserved);
    }
    dataset(const std::vector<std::vector<float>>& inputs, const std::vector<std::vector<float>>& outputs)
        : dataset((int)inputs[0].size(), (int)outputs[0].size(), (int)inputs.size()) // std::vector could be the problem
    {
        if (inputs.size() != outputs.size())
        {
            printf("--- error: inputs and outputs are not equal size ---\n");
            return;
        }

        printf("%i | %i | %i\n", (int)inputs[0].size(), (int)outputs[0].size(), (int)inputs.size());
        printf("%i | %i | %i\n", xsize, ysize, sampleCount);
    }
    ~dataset()
    {
        if (manageMemory)
        {
            if (samples != nullptr)
            {
                printf("deleting\n");
                for (int i = 0; i < sampleCount; ++i)
                {
                    samples[i].clear();
                }
                free(samples);
            }
        }
    }

    void reserve(const int reserved)
    {
        this->~dataset(); // <-- when removing this line it works

        sampleCount = reserved;
        samples = (sample*)malloc(sampleCount * sizeof(sample));

        for (int i = 0; i < sampleCount; ++i)
        {
            new (&samples[i]) sample(xsize, ysize);
        }
    }
};

int main()
{
    printf("A\n");
    fflush(stdout);
    std::vector<std::vector<float>> inputs = { {1.0f, 0.0f}, { 0.0f, 1.0f} };
    std::vector<std::vector<float>> outputs = { {0.0f, 1.0f}, { 1.0f, 0.0f } };
    dataset data(inputs, outputs);
    printf("B\n");

    // output:
    // A
    // 2 | 2 | 2
    // 1186659328 | 32766 | 2
    // B
    // deleting

    // output (with extra printf):
    // A
    // 2 == 2
    // 2 | 2 | 2
    // 2 | 2 | 2
    // B
    // deleting
}

答案1

得分: 3

析构函数很特殊。它们不是普通成员函数。特别是它们结束了调用它们的对象的生命周期。

一旦你结束了对象的生命周期,你就不允许再引用该对象或使用指针/glvalues 指向该对象,除非是一些非适用于这里的非常有限的方式。

但是,在调用 this->~dataset(); 结束 *this 的生命周期后,你立即尝试赋值给它的成员。在对象的生命周期外访问成员是未定义行为。

如果你需要重置对象的状态,那么将析构函数中的内容移到一个普通成员函数中,并在析构函数和 reserve 函数中调用该函数,而不是依赖于析构函数本身。

英文:

Destructors are special. They are not normal member functions. In particular they end the lifetime of the object on which they are called.

Once you have ended the lifetime of an object you are not allowed to refer to that object or use pointers/glvalues to that object any more, except in very narrow ways that are not applying here.

But, after you call this->~dataset(); and end the lifetime of *this, you immediately try to assign to its members. Accessing a member outside the lifetime of the object is undefined behavior.

If you need to reset the state of the object, then move the contents of the destructor to a normal member function and call that function in the destructor and in the reserve function instead of relying on the destructor itself.

huangapple
  • 本文由 发表于 2023年5月18日 05:42:26
  • 转载请务必保留本文链接:https://go.coder-hub.com/76276388.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定