Segmentação do Windows: _beginthread vs _beginthreadex vs CreateThread C ++

133

Qual é a melhor maneira de iniciar um thread _beginthread, _beginthreadxou CreateThread?

Eu estou tentando determinar quais são as vantagens / desvantagens de _beginthread, _beginthreadexe CreateThread. Todas essas funções retornam um identificador de thread para um thread recém-criado; eu já sei que o CreateThread fornece um pouco de informação extra quando ocorre um erro (pode ser verificado chamando GetLastError) ... mas quais são algumas coisas que devo considerar quando eu ' m usando essas funções?

Como estou trabalhando com um aplicativo do Windows, a compatibilidade entre plataformas já está fora de questão.

Examinei a documentação do msdn e simplesmente não consigo entender, por exemplo, por que alguém decidiria usar _beginthread em vez de CreateThread ou vice-versa.

Felicidades!

Atualização: OK, obrigado por todas as informações, eu também li em alguns lugares que não posso ligar WaitForSingleObject()se eu usasse _beginthread(), mas se eu ligar _endthread()no segmento, isso não funcionaria? Qual é o problema lá?

Kiril
fonte
2
Aqui está uma análise do que _beginthreadex () faz pelos programadores de C / C ++ que eu encontrei em um link no site de Eli Bendersky. Isto é de uma sessão de perguntas e respostas sobre se deve usar CreateThread () ou não. microsoft.com/msj/0799/win32/win320799.aspx
Richard Chambers

Respostas:

96

CreateThread() é uma chamada bruta da API do Win32 para criar outro encadeamento de controle no nível do kernel.

_beginthread()& _beginthreadex()são chamadas de biblioteca de tempo de execução C que chamam CreateThread()nos bastidores. Depois de CreateThread()retornar, _beginthread/ex()cuida da contabilidade adicional para tornar a biblioteca de tempo de execução C utilizável e consistente no novo encadeamento.

No C ++, você quase certamente deve usar, a _beginthreadex()menos que não esteja vinculando à biblioteca de tempo de execução C (também conhecida como MSVCRT * .dll / .lib).

Drew Hall
fonte
39
Isso não é mais tão verdadeiro como costumava ser. O CRT funcionará corretamente em um thread criado por CreateThread (), com exceção da função de sinal (). Haverá um pequeno vazamento de memória (~ 80 bytes) para cada thread criado com CreateThread () que usa o CRT, mas funcionará corretamente. Veja para obter mais informações: support.microsoft.com/default.aspx/kb/104641
John Dibling
1
@ John: Na verdade que o bug se aplica somente até MSVC ++ 6.0
bobobobo
5
@obobobo: Boa pergunta. Só posso especular que a MS originalmente pretendia que as _beginrotinas fossem chamadas internas e CreateThreaddeveria ser a função da API que todos chamariam. Outra possível explicação é que a EM tem uma longa e gloriosa história de ignorar o padrão e de tomar decisões muito ruins sobre nomear coisas.
John Dibling 23/08/10
15
As _beginfunções começam com um sublinhado porque a Microsoft começou a seguir o padrão mais de perto. No tempo de execução C, os nomes que estão com sublinhado são reservados para a implementação (e a implementação pode documentá-los para uso do usuário final, como esses). beginthreadex()é um nome que é permitido para o usuário usar. Se o tempo de execução C o utilizasse, ele poderia entrar em conflito com um símbolo de usuário final que o usuário tinha o direito legítimo de poder usar. Observe que as APIs do Win32 não fazem parte do tempo de execução C e usam o espaço para nome do usuário.
Michael Burr
2
@Lothar: Não são as diferenças entre a chamada de API Win32 CreateThreade as chamadas CRT _beginthread/ex, e ao chamar a CRT em um segmento, ele deve sempre ser criado com _beginthread/ex. Pode não haver mais vazamentos de memória, se você não tiver. Mas você certamente não terá seu ambiente de ponto flutuante inicializado corretamente ao chamar CreateThread, por exemplo. Há mais : "Se um thread criado usando CreateThread chama o CRT, o CRT pode encerrar o processo em condições de pouca memória".
11nspectable
37

Existem várias diferenças entre _beginthread()e _beginthreadex(). _beginthreadex()foi feito para agir mais como CreateThread()(em ambos os parâmetros e como se comporta).

Como Drew Hall menciona, se você estiver usando o tempo de execução C / C ++, deverá usar _beginthread()/ em _beginthreadex()vez de, CreateThread()para que o tempo de execução possa executar sua própria inicialização de encadeamento (configurando o armazenamento local do encadeamento etc.).

Na prática, isso significa que CreateThread()praticamente nunca deve ser usado diretamente pelo seu código.

Os documentos do MSDN para _beginthread()/ _beginthreadex()têm bastante detalhes sobre as diferenças - uma das mais importantes é que, já que o identificador de thread de um thread criado por _beginthread()é fechado automaticamente pelo CRT quando o thread sai ", se o thread gerado por _beginthread sair rapidamente, o identificador retornado ao chamador de _beginthread pode ser inválido ou, pior, apontar para outro encadeamento ".

Aqui está o que os comentários _beginthreadex()na fonte do CRT têm a dizer:

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).

Atualização em janeiro de 2013:

O CRT para VS 2012 tem um bit adicional de inicialização realizado em _beginthreadex(): se o processo for um "aplicativo empacotado" (se algo útil for retornado GetCurrentPackageId()), o tempo de execução inicializará o MTA no thread recém-criado.

Michael Burr
fonte
3
Não são momentos adequados durante CreateThread () é garantido, mas honestamente, você realmente tem que sair de sua maneira de fazê-lo. Estamos falando de uma completa falta de algo portátil e escrevendo uma DLL ou aplicativo da API WIN32 exclusivamente. Incluindo nenhuma chamada em tempo de execução C. Mesmo o uso de STL é limitado, pois você precisa fornecer alocadores personalizados para usar as funções de gerenciamento de memória WIN32. A configuração para fazer isso com o Developer Studio é um trabalho em si, mas para uma lib apenas WIN32 com o menor espaço possível, isso pode ser feito. Mas sim, não é provável que quase todos, exceto alguns muito selecionados.
WhozCraig
1
@WhozCraig: Existem limitações mais graves ao omitir o CRT. Os mais proeminentes são: sem suporte de número inteiro de 64 bits, sem suporte a ponto flutuante e - mais drasticamente - sem manipulação de exceções. Isso realmente significa que não há exceção - de maneira alguma . Nem mesmo exceções SEH. Isso é particularmente difícil de compensar, e as chances de CreateThreadser a coisa certa são cada vez menores.
IInspectable
@ MichaelBurr: Você pode atualizar sua resposta para o VC ++ 2015 .
user541686
@Mehrdad: Quais mudanças especificamente você acha que vale a pena mencionar?
IInspectable
Descobri que DisableThreadLibraryCalls não tem efeito nos threads criados com CreateThread, mas desativa os threads criados com _beginthread ou _beginthreadex.
SPLatten
23

Em geral, a coisa certa a fazer é chamar _beginthread()/_endthread()(ou as ex()variantes). No entanto, se você usar o CRT como uma DLL, o estado do CRT será inicializado e destruído adequadamente, pois os CRT DllMainserão chamados com DLL_THREAD_ATTACHe DLL_THREAD_DETACHao chamar CreateThread()e / ExitThread()ou retornar, respectivamente.

O DllMaincódigo para o CRT pode ser encontrado no diretório de instalação do VS em VC \ crt \ src \ crtlib.c.

MSN
fonte
Ótimo ponto de partida. Com um pouco de depuração, é possível mostrar que __CRTDLL_INIT é chamado mesmo para um CRT vinculado estaticamente. A pilha de chamadas que o init é chamada _LdrpCallInitRoutine @ 16 (), não sei exatamente por qual mecanismo. Isso significa que, com o CRT recente, toda a inicialização / desinicialização é feita corretamente, com exceção do tratamento de sinal, que ainda é feito na função auxiliar _threadstartex chamada de beginthread, mas não de CreateThread. Talvez você possa adicionar isso à resposta e eu concederei a recompensa?
Suma
Recompensa concedida, pois isso parece mais útil. Ainda assim, talvez valha a pena atualizar a resposta. Se você não conseguir, talvez eu o revise em alguns dias.
Suma
1
@MSN: Esteja ciente de que CreateThread ainda é ruim em uma DLL, se você estiver vinculando novamente o CRT estático e tiver chamado DisableThreadLibraryCalls, que desativa as chamadas para DLL_THREAD_DETACH. Então você terá vazamentos de memória. Isso está documentado aqui no meu artigo KB: support.microsoft.com/kb/555563/en-us
Jochen Kalmbach
17

Este é o código no núcleo de _beginthreadex(consulte 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;
    }

O restante _beginthreadexinicializa a estrutura de dados por thread para CRT.

A vantagem de usar _beginthread*é que suas chamadas CRT do thread funcionarão corretamente.

Constantin
fonte
12

Você deve usar _beginthreadou _beginthreadexpermitir que a biblioteca de tempo de execução C faça sua própria inicialização do encadeamento. Somente programadores de C / C ++ precisam saber disso, pois agora devem ter as regras de uso de seu próprio ambiente de desenvolvimento.

Se você usar _beginthread, não precisará ligar, CloseHandlepois o RTL fará por você. É por isso que você não pode esperar no identificador se tiver usado _beginthread. Também _beginthreadgera confusão se a função de thread sair imediatamente (rapidamente), pois o thread de inicialização pode ficar segurando um identificador de thread inválido no thread que ele acabou de lançar.

_beginthreadexidentificadores podem ser usados ​​para espera, mas também exigem uma chamada explícita para CloseHandle. Isso faz parte do que os torna seguros para uso com espera. Há outra questão para torná-lo completamente infalível é sempre iniciar o encadeamento suspenso. Verifique se há êxito, identificador de registro etc. O encadeamento do resumo. Isso é necessário para impedir que um encadeamento seja finalizado antes que o encadeamento de lançamento possa gravar seu identificador.

A melhor prática é usar _beginthreadex, iniciar a suspensão e continuar depois que a alça de gravação, esperar na alça estiver OK, CloseHandledeve ser chamada.

jarcher7
fonte
8

CreateThread()costumava ter vazamentos de memória quando você usa qualquer função CRT no seu código. _beginthreadex()tem os mesmos parâmetros CreateThread()e é mais versátil do que _beginthread(). Então eu recomendo que você use _beginthreadex().

Jaywalker
fonte
2
1999 artigo, pode desde foram corrigidos
bobobobo
1
Este artigo de 2005 ainda confirma que há um problema.
Jaywalker
2
Sim, isso se aplica apenas ao MSVC ++ 6.0 Service Pack 5 e versões anteriores. (consulte "Aplica-se a" menu suspenso expansível). Isso não é um problema hoje se você estiver usando o VC7 ou superior.
bobobobo
1
Isso ainda é um problema, se você vincular novamente o CRT estático! Além disso, ainda é um problema se você chamar DisableThreadLibraryCalls em uma DLL que está vinculada estaticamente; veja meu artigo KB: support.microsoft.com/kb/555563/en-us
Jochen Kalmbach
2
Você deturpou a informação: CreateThreadnão nunca vazar memória. É o CRT que faz isso, quando chamado de um thread que não foi inicializado corretamente.
esperado em
6

Com relação à sua pergunta atualizada: "Eu também li em alguns lugares que não posso ligar WaitForSingleObject()se eu usasse _beginthread(), mas se eu ligar _endthread()no segmento, isso não funcionaria?"

Em geral, você pode passar um identificador de segmento para WaitForSingleObject()(ou outras APIs que esperam nos identificadores de objeto) para bloquear até que o segmento seja concluído. Mas o identificador de encadeamento criado por _beginthread()é fechado quando _endthread()é chamado (o que pode ser feito explicitamente ou implicitamente pelo tempo de execução quando o procedimento de encadeamento retorna).

O problema é mencionado na documentação para WaitForSingleObject():

Se esse identificador estiver fechado enquanto a espera ainda estiver pendente, o comportamento da função será indefinido.

Michael Burr
fonte
5

Observando as assinaturas de funções, CreateThreadé quase idêntico a _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 
);

As observações aqui dizem que _beginthreadpode usar uma convenção __cdeclou __clrcallchamada como ponto de partida e _beginthreadexpode usar uma __stdcallou outra __clrcallpara o ponto de partida.

Acho que todos os comentários feitos sobre vazamentos de memória CreateThreadtêm mais de uma década e provavelmente devem ser ignorados.

Curiosamente, ambas as _beginthread*funções realmente chamam CreateThreada atenção C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\crt\srcna minha máquina.

// 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;
}
bobobobo
fonte
2
Comente, por que você não deve chamar CreateThread e misturar chamadas CRT nesse segmento (definitivamente não tem uma década e definitivamente não deve ser ignorado) : "Se um segmento criado usando CreateThread chamar o CRT, o CRT poderá encerrar o processo em condições de pouca memória ".
11nspectable
3

beginthreadexfornece um tópico HANDLEpara uso WaitForSingleObjecte amigos. beginthreadnão. Não se esqueça de CloseHandle()quando terminar. A resposta real seria usar boost::threadou em breve a classe de threads do C ++ 09.

MD XF
fonte
A descrição do msdn diz que "Se for bem-sucedida, cada uma dessas funções retornará um identificador para o thread recém-criado;" referindo-se _beginthread () e _beginthreadex () ...
Kiril
@Kiril: mas, em seguida, a documentação continua a dizer que _beginthread fecha o identificador para você, o que significa que você não pode usá-lo se as saídas de rosca rapidamente ...
Roger Lipscombe
2

Em comparação com _beginthread, _beginthreadexvocê pode:

  1. Especifique atributos de segurança.
  2. Inicie um encadeamento no estado suspenso.
  3. Você pode obter o ID do thread que pode ser usado OpenThread.
  4. O identificador de encadeamento retornado é garantido para ser válido se a chamada foi bem-sucedida. Lá para você precisa fechar o identificador com CloseHandle.
  5. O identificador de encadeamento retornado pode ser usado com APIs de sincronização.

A _beginthreadexse assemelha CreateThread, mas o primeiro é uma implementação CRT eo último uma chamada API do Windows. A documentação para CreateThread contém a seguinte recomendação:

Um thread em um executável que chama a biblioteca de tempo de execução C (CRT) deve usar as funções _beginthreadexe _endthreadexpara gerenciamento de threads em vez de CreateThreade ExitThread; isso requer o uso da versão multithread do CRT. Se um encadeamento criado usando CreateThreado CRT, o CRT poderá encerrar o processo em condições de pouca memória.

Vishal
fonte
De acordo com a especificação da API, os pontos de marcador 3-5 não são exclusivos _beginthreadex. Você pode converter o uintptr_tretorno de ambas as funções para HANDLE.
Andon M. Coleman
Sim, você está certo em teoria. Na prática, a diferença é que _beginthreadfecha a alça na saída. Portanto, você não pode usar o identificador de maneira confiável com as APIs de sincronização ou obter o ID do encadeamento até e a menos que use outra maneira de sincronizar e duplicar o identificador. Mas então está _beginthreadexfazendo isso por você.
Vishal
2

CreateThread()antes era um não-não, porque o CRT seria inicializado / limpo incorretamente. Mas isso agora é história: agora é possível (usando o VS2010 e provavelmente algumas versões anteriores) ligar CreateThread()sem interromper o CRT.

Aqui está a confirmação oficial do MS . Ele declara uma exceção:

Na verdade, a única função que não deve ser usada em um encadeamento criado com CreateThread()é a signal()função

No entanto, do ponto de vista da consistência, eu pessoalmente prefiro continuar usando _beginthreadex().

Serge Wautier
fonte
Embora eu suponha que isso seja verdade, você pode fornecer alguma evidência autorizada - vinculando-se à MS Documentation ou analisando as fontes CRT _beginthreadex / _endthreadex?
Suma
@Suma, eu acho que eu adicionei o MS vinculá-lo enquanto você estava escrevendo o seu comentário ;-)
Serge Wautier
O documento ao qual você está vinculando não parece confirmar: "No entanto, dependendo de como as funções CRT são chamadas, pode haver um pequeno vazamento de memória quando os threads são encerrados.". Isso significa que não é mais um grande e geral não-não, mas ainda um não-não, se você estiver criando threads com freqüência e usando essas funções neles. No entanto, o documento é de 2005 e, portanto, não pode abordar o estado recente do assunto.
Suma
1
Hmm ... embora possa depender do caso de uso, uma função que deixa um vazamento de memória, não importa qual tamanho, eu consideraria um não-não ... - especialmente se houver uma alternativa sem vazamento!
alk
"Agora é possível chamar CreateThread () sem interromper o CRT." - Infelizmente, isso não é verdade, e nunca foi. From CreateThread : "Um thread em um executável que chama a biblioteca de tempo de execução C (CRT) deve usar as funções _beginthreadex e _endthreadex para gerenciamento de threads [...] Se um thread criado usando CreateThread chama o CRT, o CRT pode terminar o processo em condições de pouca memória ".
11nspectable
2

CreateThread()é a chamada da API do Windows com idioma neutro. Ele apenas cria um objeto OS - thread e retorna HANDLE para esse thread. Todos os aplicativos do Windows estão usando esta chamada para criar threads. Todos os idiomas evitam chamadas diretas à API por razões óbvias: 1. Você não deseja que seu código seja específico do sistema operacional 2. Você precisa fazer algumas tarefas domésticas antes de chamar a API: converter parâmetros e resultados, alocar armazenamento temporário etc.

_beginthreadex()é um invólucro em C CreateThread()que responde por C específico. Ele permite que os C-ns originais com thread único funcionem em ambiente multithread, alocando armazenamento específico de thread.

Se você não usa o CRT, não pode evitar uma ligação direta para CreateThread(). Se você usa o CRT, deve usar _beginthreadex()ou algumas cadeias CRT f-ns podem não funcionar corretamente antes do VC2005.

SKV
fonte
1

CreateThread()é a chamada direta do sistema. Foi implementado no Kernel32.dllqual, provavelmente, seu aplicativo já estará vinculado por outros motivos. Está sempre disponível em sistemas Windows modernos.

_beginthread()e _beginthreadex()são funções de invólucro no Microsoft C Runtime ( msvcrt.dll). As diferenças entre as duas chamadas estão indicadas na documentação. Portanto, está disponível quando o Microsoft C Runtime está disponível ou se o seu aplicativo está vinculado estaticamente a ele. Você provavelmente também vinculará a essa biblioteca, a menos que esteja codificando na API pura do Windows (como eu sempre faço).

Sua pergunta é coerente e, na verdade, recorrente. Como muitas APIs, existem funcionalidades duplicadas e ambíguas na API do Windows com as quais temos de lidar. O pior de tudo é que a documentação não esclarece o problema. Suponho que a _beginthread()família de funções foi criada para melhor integração com outras funcionalidades C padrão, como a manipulação de errno. _beginthread()assim, integra-se melhor ao tempo de execução C.

Apesar disso, a menos que você tenha boas razões para usar _beginthread()ou _beginthreadex(), você deve usá-lo CreateThread(), principalmente porque você pode ter uma dependência a menos de biblioteca no seu executável final (e para o MS CRT isso importa um pouco). Você também não tem código de quebra de linha na chamada, embora esse efeito seja insignificante. Em outras palavras, acredito que o principal motivo para continuar CreateThread()é que não há um bom motivo _beginthreadex()para começar. As funcionalidades são precisamente, ou quase, as mesmas.

Uma boa razão para usar _beginthread() seria (como parece ser falso) que os objetos C ++ seriam desenrolados / destruídos adequadamente se _endthread()fossem chamados.

alecov
fonte
Não há chamadas de função ambíguas em tudo . CreateThreadé a chamada da API do Windows para criar um encadeamento. Se você estiver usando o CRT (porque está programando em C ou C ++), deverá criar threads usando as _beginthread[ex]chamadas do CRT (que chamam CreateThreadalém de executar a inicialização necessária do CRT). A diferença mais importante entre _beginthreade a ex-variante: o primeiro mantém a propriedade do identificador de encadeamento nativo, enquanto o último passa a propriedade para o chamador.
11nspectable
Nitpick: nãomsvcrt.dll é a DLL de tempo de execução C! Veja blogs.msdn.microsoft.com/oldnewthing/20140411-00/?p=1273
Govind Parmar
0

As outras respostas falham ao discutir as implicações da chamada de uma função de tempo de execução C que envolve uma função da API do Win32. Isso é importante ao considerar o comportamento de bloqueio do carregador DLL.

Independentemente de _beginthread{ex}qualquer gerenciamento especial de thread / memória de fibra de tempo de execução C discutir ou não, ele é implementado (assumindo o vínculo dinâmico com o tempo de execução C) em uma DLL que os processos ainda não tenham carregado.

Não é seguro ligar _beginthread*de DllMain. Eu testei isso escrevendo uma DLL carregada usando o recurso "AppInit_DLLs" do Windows. A chamada em _beginthreadex (...)vez de CreateThread (...)faz com que MUITAS partes importantes do Windows parem de funcionar durante a inicialização como os DllMaindeadlocks do ponto de entrada que aguardam a liberação do bloqueio do carregador para executar determinadas tarefas de inicialização.

Aliás, é também por isso que o kernel32.dll possui muitas funções de seqüência de caracteres sobrepostas que o tempo de execução C também possui - use-as DllMainpara evitar o mesmo tipo de situação.

Andon M. Coleman
fonte
0

Se você ler o livro Debugging Windows Application From Jeffrey Richter, ele explica que quase todas as instâncias você deve ligar em _beginthreadexvez de ligar CreateThread. _beginthreadé apenas um invólucro simplificado _beginthreadex.

_beginthreadexinicializa determinados CRT (C RunTime) internos que a CreateThreadAPI não faria.

Uma conseqüência se você usar a CreateThreadAPI em vez de usar _begingthreadexchamadas para funções CRT poderá causar problemas inesperados.

Confira este antigo Microsoft Journal da Richter.

Ehsan Samani
fonte
-2

Não há mais diferença entre os dois.

Todos os comentários sobre vazamentos de memória etc. são baseados em versões muito antigas do <VS2005. Eu fiz alguns testes de estresse anos atrás e poderia desmascarar esse mito. Até a Microsoft combina os estilos em seus exemplos, quase nunca usando _beginthread.

Lothar
fonte
CreateThread : "Se um thread criado usando CreateThread chama o CRT, o CRT pode encerrar o processo em condições de pouca memória."
11nspectable
Com base no subsentence "requer o uso da versão multithread do CRT", presumo que isso seja um lixo de documentação, pois não há mais uma versão multithread crt e há muitos anos.
Lothar
"não há mais versão CRT multithread" - O MSDN alega que "[o] CRT de thread único não está mais disponível". Vocês dois não podem estar certos. Também irei com o MSDN aqui.
11nspectable
Foi um erro de digitação, é claro que eu quis dizer que o encadeamento único se foi e o multithread se tornou o padrão e o que se passou é a distinção entre usar ou não usar threads.
Lothar
Isso está realmente ficando estranho. Agora você está usando uma declaração, sem dúvida correta ( "requer o uso da versão multithread da CRT" ) para alegar que tanto essa declaração quanto o restante da documentação provavelmente estão errados? Essa certeza não parece certa.
11nspectable