Нарізка Windows: _beginthread vs _beginthreadex vs CreateThread C ++


133

Що є кращим способом запустити нитку _beginthread, _beginthreadxабо CreateThread?

Я намагаюся визначити , які переваги / недоліки _beginthread, _beginthreadexі CreateThread. Усі ці функції повертають ручку потоку до новоствореного потоку, я вже знаю, що CreateThread надає трохи додаткової інформації, коли виникає помилка (це можна перевірити, зателефонувавши GetLastError) ... але які речі я повинен врахувати, коли я ' м, використовуючи ці функції?

Я працюю з додатком Windows, тому сумісність між платформами вже не йдеться.

Я пройшов документацію msdn і просто не можу зрозуміти, наприклад, чому хтось вирішить використовувати _beginthread замість CreateThread або навпаки.

Ура!

Оновлення: ОК, дякую за всю інформацію, я також читав у кількох місцях, що не можу зателефонувати, WaitForSingleObject()якщо використовував _beginthread(), але якщо я дзвоню _endthread()в потоці, чи не так це? Яка угода там?


2
Ось аналіз того, що робить _beginthreadex () для програмістів на C / C ++, який я знайшов за посиланням на веб-сайті Елі Бендерського. Це з питань запитань щодо того, чи слід використовувати CreateThread () чи ні. microsoft.com/msj/0799/win32/win320799.aspx
Річард Чемберс

Відповіді:


96

CreateThread() - це необмежений виклик API Win32 для створення ще одного потоку управління на рівні ядра.

_beginthread()& _beginthreadex()є дзвінками бібліотеки програми C, які дзвонять CreateThread()за кадром. Після того, як CreateThread()повернувся, _beginthread/ex()піклується про додаткову бухгалтерії , щоб зробити C бібліотека часу виконання корисної і послідовні в новому потоці.

У C ++ ви майже напевно використовуєте, _beginthreadex()якщо ви взагалі не будете посилатися на бібліотеку виконання C (він же MSVCRT * .dll / .lib).


39
Це вже не так правдиво, як раніше. CRT буде правильно функціонувати в потоці, створеному CreateThread (), за винятком функції he signal (). Буде невеликий витік пам'яті (~ 80 байт) для кожного потоку, створеного за допомогою CreateThread (), який використовує CRT, але він буде функціонувати правильно. Докладніше: support.microsoft.com/default.aspx/kb/104641
John Dibling

1
@John: Насправді ця помилка стосується лише MSVC ++ 6.0
bobobobo

5
@bobobobo: Добре запитання. Я можу лише здогадуватися, що MS спочатку призначив _beginпідпрограми внутрішніми дзвінками і CreateThreadповинен був бути функцією API, яку кожен зателефонує. Ще одне потенційне пояснення полягає в тому, що MS має довгу і славну історію ігнорування стандарту та прийняття дуже поганих рішень щодо називання речей.
Джон Дайлінг

15
Ці _beginфункції починаються з підкреслення , тому що Microsoft почав більш уважно стежити за розвитком стандарту. У процесі виконання C імена, які мають підкреслення, зарезервовані для реалізації (і реалізація може документувати їх для використання кінцевим користувачем, як і у цих). beginthreadex()- ім’я, дозволене користувачеві. Якщо C час його використання використовувався, то це може суперечити символу кінцевого користувача, який користувач мав законне право мати можливість очікувати використання. Зауважте, що API Win32 не є частиною часу виконання C, і вони використовують простір імен користувача.
Майкл Берр

2
@Lothar: Там є відмінність між API викликом Win32 CreateThreadі CRT викликами _beginthread/ex, і при виклику CRT на волосині, він завжди повинен бути створений _beginthread/ex. Можливо, більше не буде витоків пам'яті, якщо цього не зробити. Але ви, звичайно, не отримаєте належну ініціалізацію середовища з плаваючою точкою під час дзвінка CreateThread, Є ще : "Якщо потік, створений за допомогою CreateThread, викликає CRT, CRT може припинити процес в умовах низької пам'яті."
IIнеочікуваний

37

Існує кілька відмінностей між _beginthread()та _beginthreadex(). _beginthreadex()змушений діяти більше як CreateThread()(в обох параметрах і як він веде себе).

Як зазначає Дрю Холл , якщо ви використовуєте час виконання C / C ++, ви повинні використовувати _beginthread()/ _beginthreadex()замість того, CreateThread()щоб час виконання мав шанс виконати власну ініціалізацію потоку (налаштування локального сховища потоків тощо).

На практиці це означає, що CreateThread()майже ніколи не повинен використовуватися безпосередньо вашим кодом.

Документи MSDN для _beginthread()/ _beginthreadex()мають досить детальну інформацію про відмінності - одна з важливіших полягає в тому, що оскільки ручка потоку для потоку, створеного шляхом, _beginthread()автоматично закривається CRT при виході потоку, "якщо потік, створений _beginthread, виходить швидко, ручка, повернута до виклику _beginthread, може бути недійсною або, що ще гірше, вказує на інший потік ".

Ось що _beginthreadex()слід сказати у коментарях до джерела CRT:

Differences between _beginthread/_endthread and the "ex" versions:

1)  _beginthreadex takes the 3 extra parameters to CreateThread
  which are lacking in _beginthread():
    A) security descriptor for the new thread
    B) initial thread state (running/asleep)
    C) pointer to return ID of newly created thread

2)  The routine passed to _beginthread() must be __cdecl and has
  no return code, but the routine passed to _beginthreadex()
  must be __stdcall and returns a thread exit code.  _endthread
  likewise takes no parameter and calls ExitThread() with a
  parameter of zero, but _endthreadex() takes a parameter as
  thread exit code.

3)  _endthread implicitly closes the handle to the thread, but
  _endthreadex does not!

4)  _beginthread returns -1 for failure, _beginthreadex returns
  0 for failure (just like CreateThread).

Оновлення січня 2013 року:

CRT для VS 2012 має додатковий біт ініціалізації, який виконується в _beginthreadex(): якщо процес - це "пакується додаток" (якщо з нього повертається щось корисне GetCurrentPackageId()), час виконання буде ініціалізувати MTA на щойно створеному потоці.


3
Там є відповідні моменти , коли CreateThread () цілком обгрунтовані, але чесно вам дійсно потрібно вийти зі свого шляху , щоб зробити це. Ми говоримо про повну відсутність нічого портативного і пишемо виключно WIN32 API DLL або додаток. У тому числі немає дзвінків під час виконання C. Навіть використання STL обмежене тим, що вам потрібно надати спеціальні розподільники, щоб використовувати функції управління пам'яттю WIN32. Налаштування для цього в студії Developer - це робота сама по собі, але для єдиної в історії WIN32 віконця з найменшим можливим слідом це можна зробити. Але так, це майже не криваво майже для всіх, окрім кількох обраних.
WhozCraig

1
@WhozCraig: Існують більш суворі обмеження при пропущенні ЕПТ. Найвизначніші з них: Немає 64-бітової цілочисельної підтримки, немає підтримки з плаваючою комою і - що найбільш різко - не є винятком обробки. Це справді означає, що взагалі не обходиться виняток . Навіть винятки SEH. Це особливо важко компенсувати, і шанси закликати CreateThreadбути Правою річчю дещо менші.
Неочікувана


@Mehrdad: Які саме конкретні зміни ви вважаєте вартими згадок?
Неочікувана

Я виявив, що DisableThreadLibraryCalls не впливає на теми, створені CreateThread, але вимикає теми, створені за допомогою _beginthread або _beginthreadex.
SPlatten

23

Загалом, правильне робити - це зателефонувати _beginthread()/_endthread()(або ex()варіанти). Однак якщо ви використовуєте CRT як .dll, стан CRT буде належним чином ініціалізований та знищений, оскільки CRT DllMainбуде викликано відповідно DLL_THREAD_ATTACHі DLL_THREAD_DETACHпід час виклику CreateThread()та ExitThread()повернення відповідно.

DllMainКод CRT можна знайти в каталозі установки для VS під VC \ кінескопні \ SRC \ crtlib.c.


Відмінна відправна точка. З невеликою налагодженням можна показати, що __CRTDLL_INIT викликається навіть для статично пов'язаної CRT. Callstack init викликається з _LdrpCallInitRoutine @ 16 (), я не впевнений, яким саме механізмом. Це означає, що в останній CRT вся ініціалізація / деініціалізація виконується правильно, за винятком обробки сигналів, що все ще виконується в допоміжної функції _threadstartex, викликаної з startthread, але не з CreateThread. Можливо, ви могли б це додати у відповідь, і я нагороду отримаю?
Сума

Бонус нагороджений, оскільки це здається найбільш корисним. Але відповідь, можливо, варто буде оновити. Якщо ви не можете цього зробити, я можу переглянути його протягом декількох днів.
Сума

1
@MSN: Будь ласка, майте на увазі, що CreateThread все ще погано діє в DLL, якщо ви посилаєтесь на статичний CRT і викликали DisableThreadLibraryCalls, який вимикає виклики для DLL_THREAD_DETACH. Тоді ви отримаєте витоки пам’яті. Це задокументовано тут у моїй статті KB: support.microsoft.com/kb/555563/en-us
Jochen Kalmbach

17

Це код в основі _beginthreadex(див. crt\src\threadex.c):

    /*
     * Create the new thread using the parameters supplied by the caller.
     */
    if ( (thdl = (uintptr_t)
          CreateThread( (LPSECURITY_ATTRIBUTES)security,
                        stacksize,
                        _threadstartex,
                        (LPVOID)ptd,
                        createflag,
                        (LPDWORD)thrdaddr))
         == (uintptr_t)0 )
    {
            err = GetLastError();
            goto error_return;
    }

Решта _beginthreadexініціалізує структуру даних по потоку для CRT.

Перевага використання _beginthread*полягає в тому, що ваші CRT-дзвінки з потоку працюватимуть правильно.


12

Вам слід використовувати _beginthreadабо _beginthreadexдозволити бібліотеці виконання C робити власну ініціалізацію потоку. Тільки C / C ++ програмісти повинні знати це, як і тепер, правила використання власного середовища розробки.

Якщо ви користуєтесь, _beginthreadвам не потрібно дзвонити, CloseHandleяк це робитиме RTL. Ось чому ви не можете чекати на ручці, якщо ви користувались _beginthread. Також _beginthreadпризводить до плутанини, якщо функція потоку вимикається негайно (швидко), оскільки запускаючий потік, що залишився, тримає недійсну ручку нитки до потоку, який він тільки що запустив.

_beginthreadexручки можна використовувати для очікування, але також вимагати явного дзвінка CloseHandle. Це є частиною того, що робить їх безпечними для використання під час очікування. Існує інша проблема, щоб зробити його абсолютно безпроблемним - це завжди запускати нитку підвішеною. Перевірте успіх, записуйте обробку тощо. Це потрібно для запобігання завершення потоку до того, як запускова нитка зможе записати свою ручку.

Найкраща практика полягає у використанні _beginthreadex, запуску призупинено, а потім відновлення після обробки запису, зачекання на обробці в порядку, CloseHandleпотрібно викликати.


8

CreateThread()використовується для витоку пам'яті, коли ви використовуєте будь-які функції CRT у своєму коді. _beginthreadex()має ті ж параметри, що CreateThread()і він більш універсальний, ніж _beginthread(). Тому я рекомендую вам скористатися _beginthreadex().


2
Стаття 1999 року, можливо, з тих пір була виправлена
bobobobo

1
Ця стаття від 2005 року все ще підтверджує наявність проблеми.
Jaywalker

2
Так, це стосується лише MSVC ++ 6.0 Service Pack 5 та новіших версій. (див. "Застосовується до" розширюваного спадного меню). Сьогодні це не проблема, якщо ви використовуєте VC7 або вище.
bobobobo

1
Це все-таки проблема, якщо ви посилаєтеся на статичну CRT! Крім того, це все ще залишається проблемою, якщо ви викликаєте DisableThreadLibraryCalls в DLL, який є статично пов'язаним; дивіться мою статтю в KB: support.microsoft.com/kb/555563/en-us
Jochen Kalmbach

2
Ви неправильно представили інформацію: CreateThreadніколи не просочується пам'ять. Це, швидше, CRT, коли він викликається з потоку, який не був ініціалізований належним чином.
ІІНеочікувана

6

Щодо вашого оновленого запитання: "Я також читав у кількох місцях, що не можу зателефонувати, WaitForSingleObject()якщо використовував _beginthread(), але якщо я дзвоню _endthread()в потоці, чи не так?"

Загалом, ви можете передати ручку потоку до WaitForSingleObject()(або інших API, які чекають на об’єктних ручках), щоб заблокувати, поки нитка не завершиться. Але створена командою потоку _beginthread()закривається, коли _endthread()викликається (що може бути зроблено явно або зроблено неявно до часу виконання, коли процедура потоку повертається).

Проблема викликана в документації для WaitForSingleObject():

Якщо ця ручка закрита, поки очікування все ще триває, поведінка функції не визначено.


5

Дивлячись на підписи функції, CreateThreadмайже ідентичний _beginthreadex.

_beginthread,_beginthreadx vsCreateThread

HANDLE WINAPI CreateThread(
  __in_opt   LPSECURITY_ATTRIBUTES lpThreadAttributes,
  __in       SIZE_T dwStackSize,
  __in       LPTHREAD_START_ROUTINE lpStartAddress,
  __in_opt   LPVOID lpParameter,
  __in       DWORD dwCreationFlags,
  __out_opt  LPDWORD lpThreadId
);

uintptr_t _beginthread( 
   void( *start_address )( void * ),
   unsigned stack_size,
   void *arglist 
);

uintptr_t _beginthreadex( 
   void *security,
   unsigned stack_size,
   unsigned ( *start_address )( void * ),
   void *arglist,
   unsigned initflag,
   unsigned *thrdaddr 
);

У зауваженнях, наведених тут , _beginthreadможе використовуватися __cdeclабо __clrcallвихідна умова як вихідна точка, а також _beginthreadexможе бути використана __stdcallабо __clrcallпочаткова точка.

Я думаю, що будь-які зауваження людей, висловлених щодо витоків пам’яті, CreateThreadвже більше десяти років, і, ймовірно, їх слід ігнорувати.

Цікаво, що обидві _beginthread*функції насправді дзвонять CreateThreadпід капотом C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\crt\srcна моїй машині.

// From ~line 180 of beginthreadex.c
/*
 * Create the new thread using the parameters supplied by the caller.
 */
if ( (thdl = (uintptr_t)
      CreateThread( (LPSECURITY_ATTRIBUTES)security,
                    stacksize,
                    _threadstartex,
                    (LPVOID)ptd,
                    createflag,
                    (LPDWORD)thrdaddr))
         == (uintptr_t)0 )
{
        err = GetLastError();
        goto error_return;
}

2
Прокоментуйте, чому ви не повинні викликати CreateThread і змішувати в CRT виклики цього потоку (напевно, це не десятиліття, і його точно не слід ігнорувати) : "Якщо потік, створений за допомогою CreateThread, викликає CRT, CRT може припинити процес у умови низької пам'яті ".
Неочікуваний

3

beginthreadexдає вам нитку HANDLEдля використання у WaitForSingleObjectта друзях. beginthreadне робить. Не забувайте, CloseHandle()коли закінчите. Справжньою відповіддю було б скористатися boost::threadабо скоро використовувати клас потоку C ++ 09.


Опис msdn говорить про те, що "У разі успіху кожна з цих функцій повертає ручку новоствореному потоку;" посилаючись на _beginthread () та _beginthreadex () ...
Кирило

@Kiril: але далі в документації йдеться про те, що _beginthread закриває ручку для вас, це означає, що ви не можете використовувати її, якщо нитка швидко виходить ...
Roger Lipscombe

2

У порівнянні _beginthreadз _beginthreadexвами ви можете:

  1. Вкажіть атрибути безпеки.
  2. Запустіть нитку в підвішеному стані.
  3. Ви можете отримати ідентифікатор потоку, з яким можна використовувати OpenThread.
  4. Повернута ручка потоку гарантовано буде дійсною, якщо виклик був успішним. Там вам потрібно закрити ручку CloseHandle.
  5. Повернута ручка потоку може використовуватися з API синхронізації.

Це _beginthreadexдуже нагадує CreateThread, але перший - це реалізація CRT, а другий - виклик API API. Документація для CreateThread містить таку рекомендацію:

Потік у виконуваному файлі, який викликає бібліотеку часу роботи C (CRT), повинен використовувати функції _beginthreadexта _endthreadexфункції для управління потоками, а не CreateThreadта ExitThread; це вимагає використання багатопотокової версії CRT. Якщо потік, створений за допомогою CreateThreadвикликів CRT, CRT може припинити процес в умовах низької пам'яті.


Відповідно до специфікації API, пункти 3-5 куль не властиві _beginthreadex. Ви можете передавати uintptr_tповернення з обох функцій до HANDLE.
Андон М. Коулман

Так, ви праві в теорії. На практиці різниця полягає в тому, що _beginthreadзакриває ручку при виході. Таким чином, ви не можете надійно використовувати ручку з API синхронізації або отримати ідентифікатор потоку до тих пір, поки ви не скористаєтесь іншим способом синхронізації та копіювання ручки. Але тоді це робиться _beginthreadexза вас.
Вішал

2

CreateThread()колись був ні-ні, оскільки ЕПТ буде неправильно ініціалізований / очищений. Але це тепер історія: тепер можна (за допомогою VS2010 та, мабуть, декількох версій назад) зателефонувати, CreateThread()не порушуючи CRT.

Ось офіційне підтвердження MS . У ньому зазначено один виняток:

Насправді, єдина функція, яку не слід використовувати в потоці, створеному за допомогою, CreateThread()- це signal()функція.

Однак, з точки зору послідовності, я особисто вважаю за краще продовжувати використовувати _beginthreadex().


Хоча я вважаю, що це правда, чи можете ви надати якісь авторитетні докази - або за допомогою посилання на MS Documentation, або шляхом аналізу джерел CRT _beginthreadex / _endthreadex?
Сума

@Suma, я думаю, я додав посилання MS, коли ви набирали свій коментар ;-)
Serge Wautier

Документ, до якого ви посилаєтесь, схоже, не підтверджує: "Однак, залежно від того, як викликаються функції CRT, при припиненні потоків може виникнути невеликий витік пам'яті." Це означає, що це вже не великий і загальний ні-ні, але все-таки ні-ні, якщо ви створюєте теми часто і використовуєте ці функції в них. Однак документ складається з 2005 року, і тому він не може вирішити питання останнього питання.
Сума

1
Хм ... хоча це може залежати від випадку використання, функція, яка залишає протікання пам'яті, незалежно від того, який розмір, я вважаю б ні-ні ... - зокрема, якщо є альтернатива, що не протікає!
алк

"Тепер ви можете зателефонувати CreateThread (), не порушуючи CRT." - На жаль, це неправда і ніколи не була. З CreateThread : "Потік у виконуваному файлі, який викликає бібліотеку часу запуску C (CRT), повинен використовувати функції _beginthreadex та _endthreadex для управління потоками [...] Якщо потік, створений за допомогою CreateThread, викликає CRT, CRT може припинити обробляти в умовах низької пам'яті. "
Неочікуваний

2

CreateThread()- це виклик Windows API, який не відповідає мові. Він просто створює об’єкт ОС - нитку і повертає HANDLE до цього потоку. Усі програми Windows використовують цей виклик для створення потоків. Усі мови уникають прямого виклику API з очевидних причин: 1. Ви не хочете, щоб ваш код був специфічним для ОС 2. Вам потрібно зробити деякий режим зберігання, перш ніж викликати подібний API: конвертувати параметри та результати, виділити тимчасове зберігання тощо.

_beginthreadex()- це обгортка C, CreateThread()яка відповідає специфіці C. Це дозволяє оригінальним однопоточним C f-ns працювати в багатопотоковому середовищі, виділяючи специфічне для потоку сховище.

Якщо ви не використовуєте CRT, ви не можете уникнути прямого дзвінка на CreateThread(). Якщо ви використовуєте CRT, ви повинні використовувати _beginthreadex()або деякі CRT-рядки f-ns можуть не працювати належним чином до VC2005.


1

CreateThread()- прямий системний виклик. Він реалізований, з Kernel32.dllяким, швидше за все, ваша програма вже буде пов'язана з інших причин. Він завжди доступний у сучасних системах Windows.

_beginthread()і _beginthreadex()є функцією обгортки в Microsoft C Runtime ( msvcrt.dll). Відмінність між двома дзвінками вказана в документації. Таким чином, він доступний, коли програма Microsoft C Runtime доступна або якщо ваша програма статично пов'язана з нею. Ви, ймовірно, також будете зв'язуватися з цією бібліотекою, якщо ви не кодуєте чистий API Windows (як я часто це роблю).

Ваше запитання є узгодженим і фактично повторюваним. Як і багато API, в API Windows є дублюючі та неоднозначні функціональні можливості. Найгірше, що документація не з'ясовує питання. Я припускаю, що _beginthread()сімейство функцій було створено для кращої інтеграції з іншими стандартними функціоналами C, такими як маніпулювання errno. _beginthread()таким чином, краще інтегрується із часом виконання C.

Незважаючи на це, якщо у вас немає поважних причин для використання _beginthread()або _beginthreadex(), слід використовувати CreateThread(), здебільшого тому, що ви можете отримати одну меншу залежність від бібліотеки в остаточному виконуваному файлі (а для MS CRT це не має значення). Ви також не маєте коду обгортання навколо дзвінка, хоча цей ефект незначний. Іншими словами, я вважаю, що основною причиною дотримання цього CreateThread()є те, що для початку немає жодної вагомої причини _beginthreadex(). Функціональні можливості точно або майже однакові.

Хорошим приводом для використання _beginthread() було б (як це здається помилковим), щоб об'єкти C ++ були правильно розмотані / знищені, якщо _endthread()викликали.


Однозначних дзвінків функцій взагалі немає . CreateThreadце виклик API Windows для створення потоку. Якщо ви використовуєте CRT (оскільки ви програмуєте на C або C ++), вам слід створити потоки, використовуючи _beginthread[ex]дзвінки CRT (які викликають CreateThreadдодатково до виконання необхідної ініціалізації CRT). Найважливіша відмінність між _beginthreadколишнім варіантом: Перший зберігає право власності на основну ручку потоку, а другий передає право власності абоненту.
Неочікуваний

Нітпік: msvcrt.dllце не DLL часу виконання C! Дивіться blogs.msdn.microsoft.com/oldnewthing/20140411-00/?p=1273
Govind

0

В інших відповідях не вдалося обговорити наслідки виклику функції часу виконання C, яка охоплює функцію API Win32. Це важливо, враховуючи поведінку блокування навантажувача DLL.

Незалежно від того, чи _beginthread{ex}існує якесь спеціальне управління потоком / волоконною пам’яттю C Runtime, як обговорюються інші відповіді, воно реалізується в (припускаючи динамічне посилання на час виконання C) DLL, які процеси, можливо, ще не завантажені.

Дзвонити не безпечно _beginthread* звідсиDllMain . Я перевірив це, написавши DLL, завантажений за допомогою функції Windows "AppInit_DLLs". Виклик _beginthreadex (...)замість цього CreateThread (...)призводить до того, що багато важливих частин Windows припиняє роботу під час завантаження, оскільки DllMainтупикові точки входу, які очікують звільнення блокування завантажувача для виконання певних завдань ініціалізації.

До речі, саме тому kernel32.dll має багато рядкових функцій, що перекриваються, що також виконує C-час - використовуйте ті, DllMainщоб уникнути подібних ситуацій.


0

Якщо ви читаєте книгу Налагодження програми Windows від Джеффрі Ріхтера в ній, він пояснює, що майже у всіх випадках вам потрібно зателефонувати, _beginthreadexа не телефонувати CreateThread. _beginthreadце просто спрощена обгортка навколо_beginthreadex .

_beginthreadex ініціалізує певні внутрішні протоколи CRT (C RunTime), що CreateThread API не робив би.

Наслідок, якщо ви використовуєте CreateThread API замість використання _begingthreadexдзвінків до функцій CRT, можуть несподівано спричинити проблеми.

Перевірте цей старий журнал Microsoft від Ріхтера.


-2

Ніякої різниці між обома.

Усі коментарі щодо витоків пам'яті тощо базуються на дуже старих версіях <VS2005. Я робив кілька стрес-тестувань років тому і міг розвінчати цей міф. Навіть Microsoft змішує стилі у своїх прикладах, майже ніколи не використовуючи _beginthread.


CreateThread : "Якщо потік, створений за допомогою CreateThread, викликає CRT, CRT може припинити процес в умовах низької пам'яті."
Неочікуваний

Виходячи з подання "вимагає використання багатопотокової версії CRT", я припускаю, що це документальне сміття, оскільки немає багатопоточної версії crt більше і вже багато років.
Лотар

"більше не існує багатопотокової версії crt" - MSDN стверджує, що "[t] він однопоточний CRT більше не доступний." Ви не можете бути праві. Я піду з MSDN і тут.
Неочікуваний

Це було помилковим помилкою, я, мабуть, мав на увазі, що єдиної нитки вже немає, а багатопоточна стала еталоном, і те, що пішло, - це відмінність між використанням чи не використанням ниток.
Лотар

Це дійсно стає дивним. Зараз ви використовуєте твердження, що, безсумнівно, правильно ( "вимагає використання багатопотокової версії CRT" ), щоб стверджувати, що і ця заява, і решта документації, ймовірно, неправильні? Це впевнено не звучить правильно.
Неочікуваний
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.