Форум программистов, компьютерный форум, киберфорум
C++
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.72/18: Рейтинг темы: голосов - 18, средняя оценка - 4.72
 Аватар для eva2326
1673 / 501 / 107
Регистрация: 17.05.2015
Сообщений: 1,518

Получить std::tm из time_point, или как избежать ошибок ODR, связанных с использованием time_t

13.12.2020, 19:49. Показов 3949. Ответов 1

Студворк — интернет-сервис помощи студентам
Всем привет!

Есть код:

C++
1
2
3
4
5
6
7
8
9
    // функция не делает поправки на часовой пояс
    ::std::tm getGMT(const ::std::time_t time) noexcept
    {
        ::std::tm result = {};
        const auto err = ::gmtime_s(&result, &time);
        assert(!err);
        (void) err;
        return result;
    }
Проблема в том, что тип данных std::time_t не стабильный.
Он может плавать даже на одном и том же компиляторе, с одними и теми же настройками.
Макрос _USE_32BIT_TIME_T, и его аналоги - это мины замедленного действия.

Допустим, у вас есть две и более статических библиотек.
И эти библиотеки используют сишные функции для работы со временем.
Аргументом таких функций выступает time_t.
Но одна библиотека собрана в режиме, когда time_t 32х битный, а другая - когда он 64х битный.

Главный вопрос темы: что получится в итоге после линковки?

1. Внутри отдельно взятой библиотеки компилятор построит трамплин вида:

(фрагмент взят из реализации стандартной библиотеки msvc2019)
C++
1
2
3
4
5
6
7
8
9
10
11
12
#else // ^^^ _USE_32BIT_TIME_T ^^^ // vvv !_USE_32BIT_TIME_T vvv
 
        _Check_return_ _CRT_INSECURE_DEPRECATE(ctime_s)
        static __inline char* __CRTDECL ctime(
            _In_ time_t const* const _Time
            )
        {
            #pragma warning(push)
            #pragma warning(disable: 4996)
            return _ctime64(_Time);
            #pragma warning(pop)
        }
То бишь, локальное использование сишных функций относительно безопасное.

2. Однозначно, нельзя передавать значение time_t через границы библиотек.
std::time_t взятый из одной библиотеки может оказаться несовместимым с сишными функциями другой библиотеки

3. Вызовы пользовательских функций, таких как getGMT однозначно будут приводить к UB, в связи с нарушением ODR


Второй главный вопрос темы: и как в таких условиях конструировать свои пользовательские функции getGMT ????

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

Вместо сишной библиотеки можно воспользоваться с++ версией - chrono

Однако, тут вылазиет другая проблема.
Дело в том, что в хронометражке вообще нет функций для работы с календарем.
Это - удивительно! Это примерно как "в с++ добавили автомобили, но у них нет ни руля, ни тормозов"

В итоге, полноценно пользоваться chrono в отрыве от сишной библиотеки не получится.
Потому что единственный способ работать с календарем - получить доступ к std::tm.

И вот отсюда третий главный вопрос темы: как из std::chrono::system_clock::time_point получить валидный std::tm не используя std::time_t?

Моё решение:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <cassert>
#include <chrono>
#include <limits>
#include <ctime>
 
    using clockT = ::std::chrono::system_clock;
    using time_pointT = clockT::time_point;
 
    // ВАЖНО: обязательно должна быть определена именно в хэдере
    // функция не делает поправки на часовой пояс
    static inline ::std::tm getGMT(const time_pointT point) noexcept
    {
        // здесь может быть warning из-за различий между std::time_t,
        // и типом данных, который по факту возвращает функция.
        // как правило clockT::to_time_t возвращает __time64_t
        // что может приводить к конфликтам для 32х битных time_t
        //
        // const ::std::time_t seconds = clockT::to_time_t(point); 
        
        // устраняем warning
        using limit = ::std::numeric_limits<std::time_t>;
        const auto raw = clockT::to_time_t(point);
        assert(static_cast<decltype(raw)>((limit::max)()) >= raw);
        const ::std::time_t seconds = static_cast<::std::time_t>(raw);
 
        ::std::tm result = {};
        #ifdef _MSC_VER
            const auto err = ::gmtime_s(&result, &seconds);
            assert(!err);
            (void) err;
        #else
            const auto* ptr = ::gmtime_r(&seconds, &result);
            assert(ptr);
            assert(ptr == &result);
            (void) ptr;
        #endif
        return result;
    }
 
int main()
{
    const std::tm result = getGMT(clockT::now());
    (void) result;
}
Решение основывается на двух фундаментальных свойствах с++: если функция определена в хэдере, и при этом имеет внутреннее связывание, то каждая ед. трансляции будет получать в своё распоряжение отдельную уникальную копию этой функции, которую в итоге компилятор попытается встроить по месту. Эта уникальная копия будет сгенерирована компилятором исходя из актуальных для единицы трансляции настроек сборки.

Важно: Я исхожу из предположения, что использование сишных функций безопасно, если это использование носит локальный характер. Ну то есть, переменная time_t - местная, и не пришла откуда то извне. Поэтому её плавающий размер уже никак не сможет навредить.

Четвертый и самый главный, заключительный вопрос: корректно ли моё решение?
0
Лучшие ответы (1)
Programming
Эксперт
39485 / 9562 / 3019
Регистрация: 12.04.2006
Сообщений: 41,671
Блог
13.12.2020, 19:49
Ответы с готовыми решениями:

Как привести тип time_point<std::filesystem::__file_clock, [.]>к типу const time_point<std::chrono::_V2::system_clock
Если использовать std::experimental::filesystem::v1 то можна сделать вот так : std::chrono::system_clock::time_point Point =...

Как избежать ошибок линковщика?
Я только-только начал изучать С++(Borland). Взял первый попавшийся пример из helpa - printf #include &lt;stdio.h&gt; #include...

Реализация простейшего чата: как избежать ошибок?
Я пишу штото типа чата, написал - шду ответа, но на одном компе запущу один клиент, роботает норм, запущу другой выдает ошибку. С етого и...

1
Эксперт С++
 Аватар для hoggy
8973 / 4319 / 960
Регистрация: 15.11.2014
Сообщений: 9,760
15.12.2020, 01:14
Лучший ответ Сообщение было отмечено eva2326 как решение

Решение

Цитата Сообщение от eva2326 Посмотреть сообщение
Макрос _USE_32BIT_TIME_T, и его аналоги - это мины замедленного действия.
у _USE_32BIT_TIME_T нет никаких аналогов.
макрос можно активировать только и только для 32х битных сборок.
для 64х битных сборок он запрещен.

time_t является 32х-битным если:
- у тебя 2005 студия или старше.
- у тебя сборка x86 и ты активировала _USE_32BIT_TIME_T (только под виндой для cl/mingw)
- у тебя 32х битная ОСЬ

во всех остальных случаях time_t будет 64х битным.

Цитата Сообщение от eva2326 Посмотреть сообщение
что получится в итоге после линковки?
ситуация хуже чем кажется.
если две библиотеки линкуются с разными настройками _USE_32BIT_TIME_T,
тогда поплывут все структуры, которые используют time_t.

например, в библиотеке first.lib создаётся объект структуры:
C++
1
2
3
4
struct fisrt
{
    int foo; time_t bar; int baz;
};
его данные будут некосистентными в границах библиотеки second.lib
и ничего с этим сделать нельзя.

единственный способ гарантировать корректную работу,
это собирать все библиотеки с одинаковым значением _USE_32BIT_TIME_T

Цитата Сообщение от eva2326 Посмотреть сообщение
в хронометражке вообще нет функций для работы с календарем.
для работы с календарём есть специализированная библиотека date,
которую написал тот же самый человек, что и chrono.

date полностью совместима с chrono,
и является претендентом на влючение в с++20

Цитата Сообщение от eva2326 Посмотреть сообщение
корректно ли моё решение?
корректно.

есть такой костыль лайфхак:
C++
1
2
3
    using std_clock_t = ::std::chrono::system_clock;
    using std_time_point_t = std_clock_t::time_point;
    using std_time_t  = decltype(std_clock_t::to_time_t({}));
в публичных интерфейсах используешь std_time_t вместо std::time_t

вопреки оффициально документации,
метод system_clock::to_time_t,
возвращает не оригинальный std::time_t,
а неккий __time64_t, который не зависит от значения _USE_32BIT_TIME_T.
поэтому, он идеально подходит для передачи между границами библиотек,
или модулей.
2
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
inter-admin
Эксперт
29715 / 6470 / 2152
Регистрация: 06.03.2009
Сообщений: 28,500
Блог
15.12.2020, 01:14
Помогаю со студенческими работами здесь

Как избежать ошибок понижения позиций при покупки ссылок?
Здравствуйте, уважаемые форумчане. В яндекс и в гугл не нашел однозначного ответа. Интересует ответ на вопрос.Как избежать ошибок понижения...

баг msvc, или компиляторы право имеют? (отбрасывание const для возвращаемого типа приводит к ошибке ODR)
Всем привет! Рассмотрим пример: // header void foo(); // cpp #include &lt;iostream&gt;

Аналог типа time_t в Си++ или Borland'е
Как называется аналог типа time_t в Си++ или Borland'е? Мне нужен именно тот тип, который меряет количество секунд (от 1970 года вроде). И...

Конветирование строки char в time_t или в struct tm
Есть строковые переменные типа char над которыми нужно производить вычисления (сравнение, получение разницы). Порыл сеть на предмет как...

Как получить DataTable из двух связанных таблиц?
У меня есть DataSet, включающий две связанные таблицы. Мне нужно получить на выходе DataTable - выборку из этих двух таблиц. Перерыл...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
2
Ответ Создать тему
Новые блоги и статьи
Советы по крайней бережливости. Внимание, это ОЧЕНЬ длинный пост.
Programma_Boinc 28.12.2025
Советы по крайней бережливости. Внимание, это ОЧЕНЬ длинный пост. Налог на собак: https:/ / **********/ gallery/ V06K53e Финансовый отчет в Excel: https:/ / **********/ gallery/ bKBkQFf Пост отсюда. . .
Кто-нибудь знает, где можно бесплатно получить настольный компьютер или ноутбук? США.
Programma_Boinc 26.12.2025
Нашел на реддите интересную статью под названием Anyone know where to get a free Desktop or Laptop? Ниже её машинный перевод. После долгих разбирательств я наконец-то вернула себе. . .
Thinkpad X220 Tablet — это лучший бюджетный ноутбук для учёбы, точка.
Programma_Boinc 23.12.2025
Рецензия / Мнение/ Перевод Нашел на реддите интересную статью под названием The Thinkpad X220 Tablet is the best budget school laptop period . Ниже её машинный перевод. Thinkpad X220 Tablet —. . .
PhpStorm 2025.3: WSL Terminal всегда стартует в ~
and_y87 14.12.2025
PhpStorm 2025. 3: WSL Terminal всегда стартует в ~ (home), игнорируя директорию проекта Симптом: После обновления до PhpStorm 2025. 3 встроенный терминал WSL открывается в домашней директории. . .
Как объединить две одинаковые БД Access с разными данными
VikBal 11.12.2025
Помогите пожалуйста !! Как объединить 2 одинаковые БД Access с разными данными.
Новый ноутбук
volvo 07.12.2025
Всем привет. По скидке в "черную пятницу" взял себе новый ноутбук Lenovo ThinkBook 16 G7 на Амазоне: Ryzen 5 7533HS 64 Gb DDR5 1Tb NVMe 16" Full HD Display Win11 Pro
Музыка, написанная Искусственным Интеллектом
volvo 04.12.2025
Всем привет. Некоторое время назад меня заинтересовало, что уже умеет ИИ в плане написания музыки для песен, и, собственно, исполнения этих самых песен. Стихов у нас много, уже вышли 4 книги, еще 3. . .
От async/await к виртуальным потокам в Python
IndentationError 23.11.2025
Армин Ронахер поставил под сомнение async/ await. Создатель Flask заявляет: цветные функции - провал, виртуальные потоки - решение. Не threading-динозавры, а новое поколение лёгких потоков. Откат?. . .
Поиск "дружественных имён" СОМ портов
Argus19 22.11.2025
Поиск "дружественных имён" СОМ портов На странице: https:/ / norseev. ru/ 2018/ 01/ 04/ comportlist_windows/ нашёл схожую тему. Там приведён код на С++, который показывает только имена СОМ портов, типа,. . .
Сколько Государство потратило денег на меня, обеспечивая инсулином.
Programma_Boinc 20.11.2025
Сколько Государство потратило денег на меня, обеспечивая инсулином. Вот решила сделать интересный приблизительный подсчет, сколько государство потратило на меня денег на покупку инсулинов. . . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru