Завершение времени жизни объекта, используемого в другом потоке - PullRequest
2 голосов
/ 29 января 2020

Что Стандарт может сказать о приведенном ниже коде?

#include <iostream>
#include <chrono>
#include <thread>

int main()
{
    static_assert(sizeof(int) == sizeof(float));
    using namespace std::chrono_literals;

    auto pi = new int{10};

    std::thread t([pi]() {
        for (int i = 0; i < 3; i++) {
            std::cout << *pi << '\n';
            std::this_thread::sleep_for(1s);
        }
    });

    std::this_thread::sleep_for(1s);
    auto pf = ::new (pi) float{};

    *pf = -1.0;

    t.join();
}

Мне особенно любопытно, как (или возможно) применить [basi c .life] / 7 и [basi c .life] / 8 , говоря

Аналогично, до начала срока службы объекта, но после хранилища, которое объект будет занимать был выделен или, после истек срок жизни объекта и до повторного использования или освобождения памяти, занятой объектом

и

Если после истек срок жизни объекта и до повторного использования или освобождения хранилища, которое занимал объект, в месте хранения, которое занимал исходный объект

*, создается новый объект 1023 * соответственно, учитывая, что [basi c .life] / 11 говорит

В этом разделе «до» и «после» относятся к «* 1029». * происходит до ”отношения.

Означает ли это, что если поток делает не "видит" конец времени жизни int объекта, он может получить к нему доступ, как если бы он был жив?

Сначала я подумал, что программа имеет гонку данных :

Выполнение программы содержит гонку данных , если она содержит два потенциально одновременных конфликтующих действия, по крайней мере одно из которых не атоми c, и ни одно не происходит раньше другого, за исключением специального случая для обработчиков сигналов, описанных ниже. Любая такая гонка данных приводит к неопределенному поведению.

Однако конфликтующих действий нет. По определению "конфликта" :

Две оценки выражений конфликт , если одна из них изменяет область памяти , а другая каждый читает или изменяет ту же самую ячейку памяти.

и определение "ячейки памяти" :

A ячейка памяти является либо объектом скалярного типа, либо максимальной последовательностью смежных битовых полей, все из которых имеют ненулевую ширину.

считывает *pi не конфликтует с хранилищем через *pf, потому что эти значения l обозначают разные объекты и, следовательно, разные ячейки памяти.

Мне кажется, что в программе должен быть UB, но я не вижу, где.

Ответы [ 2 ]

1 голос
/ 29 января 2020

[basi c .life] / 4 Свойства, приписываемые объектам и ссылкам в настоящем стандарте, применяются к данному объекту или ссылке только в течение срока его службы .

Термин «во время» формально не определен, но было бы разумно определить его как «после начала срока службы и до его окончания».

Теперь противоположное происходит - после не происходит - до , это « происходит - до или несинхронизировано с ». Таким образом, операция, которая происходит до или несинхронизирована с началом жизни объекта, или происходит после или не синхронизируется- с концом времени жизни объекта, не выполняется "в течение его времени жизни". В той степени, в которой такая операция опирается на «свойства, приписываемые» этому объекту, она демонстрирует неопределенное поведение.

На этом основании я полагаю, что ваш пример демонстрирует неопределенное поведение, поскольку доступ к объекту *pi равен unsynchronized-with конец его жизни.

0 голосов
/ 29 января 2020

Доступ к *pi в потоке после вычисления выражения ::new (pi) float{}; приводит к неопределенному поведению.

Из того же раздела [basic.expr], параграф 5 говорит:

Программа может завершить время жизни любого объекта, повторно используя хранилище, занимаемое объектом

Как только хранилище, выделенное для int, на которое указывает pi, повторно используется для При хранении с плавающей точкой время жизни объекта *pi закончилось. В параграфе 7 говорится, что когда поток обращается к этому значению, у вас неопределенное поведение.

В параграфе 6 приведен пример, который делает то же самое, что вы делаете, но без потока, и комментирует его является неопределенным поведением.

К общим результатам всего этого относится вероятность того, что l oop в потоке может печатать одно и то же значение все 3 раза (если он читает *pi один раз перед l oop и отображает это значение), или вы можете получить 10 один или два раза, затем целое число, представленное двоичным значением для -1.0f Но это не единственно возможные результаты.

...