
Passo 1 de 8
Identifique os dados compartilhados
Reconheça quais variáveis são acessadas tanto pela ISR quanto pelo fluxo principal antes de decidir como tratá-las.
Trilha de aprendizado · Nível 10 · Tutorial 7
Compartilhar flags e contadores entre uma ISR e o fluxo principal, distinguindo visibilidade dos acessos, atomicidade e preservação dos eventos.
Identifique os dados compartilhados
Reconheça quais variáveis são acessadas tanto pela ISR quanto pelo fluxo principal antes de decidir como tratá-las. 2 min
Entenda o que volatile garante
Use volatile em flags e contadores que podem ser modificados entre a ISR e o loop, sem confundir visibilidade com atomicidade. 2 min
Veja como uma leitura mistura dois instantes
Veja como uma interrupção pode se intercalar a uma leitura de vários bytes e produzir um valor que nunca esteve armazenado por inteiro. 3 min
Reconheça a corrida na leitura e limpeza de uma flag
Uma flag de um byte admite acessos simples, mas consultar e limpar em seguida é uma operação composta sujeita à intercalação da ISR. 2 min
Copie o contador em uma seção crítica
Proteja somente a cópia de um contador compartilhado e use o valor local fora da seção crítica. 4 min
Retire a flag em uma operação indivisível
Faça a retirada atômica de uma flag para que uma sinalização posterior da ISR permaneça disponível, mantendo o processamento fora da seção crítica. 3 min
Escolha entre flag e contador
Selecione entre flag e contador conforme a informação que precisa sobreviver entre a ISR e o loop, transferindo contagens em lote com proteção curta. 3 min
Aplique: registre na ISR e processe no loop
Conclua uma solução em que a ISR registra atendimentos do Timer1, o loop transfere a contagem com proteção e processa o resultado fora da seção crítica. 4 min

Passo 1 de 8
Reconheça quais variáveis são acessadas tanto pela ISR quanto pelo fluxo principal antes de decidir como tratá-las.
No Uno R3, o loop() executa normalmente até que uma interrupção do Timer1 seja atendida. A ISR executa, termina e o processador retorna ao ponto do loop() que foi interrompido.
Uma variável é compartilhada quando tanto a ISR quanto o fluxo principal a acessam — lendo, escrevendo ou ambos. Não importa se ela é global por conveniência: o que decide é o mapa de acessos.
Nos exemplos deste tutorial, considere o ATmega328P do Uno R3: há um único núcleo e as interrupções não se aninham. Assim, enquanto uma ISR está em execução, outra interrupção aguarda; mas a ISR ainda pode interromper o loop() entre instruções.
| Variável | ISR do Timer1 | loop() | É compartilhada? |
|---|---|---|---|
| eventos | incrementa | lê | Sim |
| atualizarLed | escreve true | lê e limpa | Sim |
| estadoLed | não acessa | lê e escreve | Não |
| amostraLocal | não acessa | lê e escreve | Não |
Use este pequeno firmware apenas para mapear os acessos; por enquanto, não altere o código. A ISR registra que houve um atendimento e solicita uma atualização. Já o loop() consulta essas informações e mantém seus próprios dados locais de trabalho.
Mapa de acessos
#include <avr/interrupt.h>
uint16_t eventos = 0;
bool atualizarLed = false;
bool estadoLed = false;
ISR(TIMER1_COMPA_vect) {
eventos++;
atualizarLed = true;
}
void loop() {
uint16_t amostraLocal = eventos;
if (atualizarLed) {
atualizarLed = false;
estadoLed = !estadoLed;
digitalWrite(LED_BUILTIN, estadoLed);
}
if (amostraLocal >= 10) {
// Uma decisão posterior usaria esta cópia.
}
}
As duas caixas centrais são compartilhadas porque recebem acessos dos dois fluxos.
Uma linha de C/C++ não é uma zona protegida. Enquanto o loop() executa uma expressão que acessa um dado compartilhado, uma interrupção habilitada pode ser atendida entre instruções geradas para aquela expressão. Após a ISR, o loop() continua.
Por isso, ao encontrar uma variável acessada pela ISR, marque também todo ponto do loop() que a lê ou escreve. Essa marcação revela onde a ordem entre os dois fluxos pode influenciar o resultado.
Ainda não vamos decidir como tratar esses acessos. Primeiro, precisamos identificá-los com precisão.
Dica
Pergunte para cada variável:
loop() lê ou escreve nela?No trecho anterior, eventos é escrito pela ISR e lido pelo loop(). atualizarLed é escrito pela ISR, depois lido e escrito pelo loop(). Já estadoLed e amostraLocal pertencem somente ao loop().
Associe cada variável do exemplo ao seu mapa de acessos.
Faça as associações corretas.
Toque em um item e depois no par correspondente.

Passo 2 de 8
Use volatile em flags e contadores que podem ser modificados entre a ISR e o loop, sem confundir visibilidade com atomicidade.
Uma variável pode ser compartilhada quando a ISR e o loop() a acessam. Mas isso, sozinho, não decide o uso de volatile.
Use volatile quando há acesso pelos dois fluxos e pelo menos um deles pode modificar a variável. Nesse caso, o valor pode mudar fora da sequência normal observada pelo outro fluxo. O compilador deve então manter as leituras e escritas previstas no código.
Se ISR e loop() apenas leem uma mesma variável que não é modificada por nenhum deles, ela pode ser compartilhada no mapa de acessos, mas não precisa de volatile por esse motivo.

volatile se aplica aos dados que podem mudar entre os fluxos, não apenas por serem acessados em dois lugares.
No exemplo iniciado no passo anterior, a ISR altera eventos e atualizarLed. O loop() lê ambos; por isso, as duas declarações usam volatile.
amostraLocal recebe uma cópia de eventos, mas depois é acessada somente pelo loop(). Ela não pode mudar por ação da ISR e não precisa do qualificador.
Variáveis modificadas pela ISR e cópia local do loop
volatile uint16_t eventos = 0;
volatile bool atualizarLed = false;
bool estadoLed = false;
ISR(TIMER1_COMPA_vect) {
eventos++;
atualizarLed = true;
}
void loop() {
uint16_t amostraLocal = eventos;
if (atualizarLed) {
// O trabalho será definido nos próximos passos.
}
// Cálculos com amostraLocal.
}Dica
Ser global não é critério para usar volatile. Uma variável global usada somente pelo loop() continua comum. Do mesmo modo, uma cópia local não ganha volatile apenas por ter vindo de um dado compartilhado.
Pergunte: esta variável pode ser modificada pela ISR enquanto o loop() a usa, ou pelo loop() enquanto a ISR a usa? Se sim, ela precisa de volatile.
volatile cuida da visibilidade dos acessos: o compilador deve realizar as leituras e escritas previstas para um dado que pode mudar fora do fluxo em execução.
Ele não torna uma sequência de várias etapas em uma ação única e não impede o atendimento da ISR. Portanto, declarar eventos como volatile é necessário, mas não é uma proteção completa para toda forma de acesso a ele.
Compare os papéis sem confundi-los.
| Propriedade | volatile garante? | Significado |
|---|---|---|
| Visibilidade do acesso | Sim | O acesso previsto não é descartado como se o valor fosse estável. |
| Atomicidade | Não | Uma operação pode envolver mais de uma etapa. |
| Exclusão da ISR | Não | A interrupção ainda pode ocorrer durante o fluxo principal. |
Complete os qualificadores das duas variáveis que a ISR pode modificar. Não acrescente qualificador à cópia local.
Verdadeiro ou falso: declarar um contador compartilhado como volatile impede que a ISR o altere durante uma leitura feita pelo loop().

Passo 3 de 8
Veja como uma interrupção pode se intercalar a uma leitura de vários bytes e produzir um valor que nunca esteve armazenado por inteiro.
No ATmega328P do Uno R3, o processador trabalha naturalmente com unidades de 8 bits. Por isso, a leitura de uma variável uint16_t na SRAM pode exigir acessos separados aos seus dois bytes; uma variável de 32 bits pode exigir ainda mais acessos.
Se uma ISR atualizar o valor entre essas partes, o loop() pode combinar bytes obtidos em instantes diferentes. O resultado é uma leitura composta incoerente: um número que nunca esteve armazenado inteiro na variável compartilhada.
volatile continua necessário para que os acessos previstos sejam realizados, mas não transforma vários acessos em uma única ação nem impede a ISR de ser atendida entre eles.
Um valor de 16 bits ocupa dois bytes. Uma interrupção pode ocorrer depois que uma parte foi lida e antes da leitura da outra parte.

O risco está no intervalo entre os acessos aos bytes; volatile não elimina esse intervalo.
Considere uma hipótese didática explícita para rastrear o problema: o loop() lê primeiro o byte baixo e depois o byte alto. Essa não é uma ordem universal garantida para todo código C/C++ compilado; ela serve para mostrar uma intercalação possível.
O contador compartilhado começa em 0x00FF. O loop() lê o byte baixo, que vale 0xFF. Em seguida, a ISR incrementa o contador para 0x0100: o byte baixo passa para 0x00 e ocorre transporte para o byte alto, que passa para 0x01. Por fim, o loop() lê o byte alto.
Exemplo
Estado inicial completo: 0x00FF
loop() lê o byte baixo e obtém 0xFF.0x00FF → 0x0100.loop() lê o byte alto e obtém 0x01.0x01 com baixo 0xFF, o loop() forma 0x01FF.Esse resultado não é o valor completo anterior (0x00FF) nem o posterior (0x0100). Ele mistura dois instantes.
A transição de 0x00FF para 0x0100 altera os dois bytes e deixa visível a origem da mistura.

As cores indicam que as duas partes usadas pelo loop vieram de estados completos diferentes.
Usando exatamente a hipótese didática da tela anterior, coloque em ordem os três acessos que produzem a leitura incoerente. O contador começa em 0x00FF.
Ordene os acessos para representar a intercalação possível.
Com base na ordem que você organizou, qual valor de 16 bits o loop() monta? Explique por que esse valor não corresponde a um instantâneo coerente e por que declarar o contador como volatile não evita essa combinação.
Escreva pelo menos 40 caracteres (0/40).
Uma leitura coerente é uma fotografia de um único instante: ela pode ficar antiga logo depois se a ISR atualizar o contador. Isso não é o mesmo que uma leitura incoerente.
No exemplo, 0x00FF seria uma fotografia coerente anterior e 0x0100 uma fotografia coerente posterior. Já 0x01FF não é uma fotografia de nenhum instante completo. No próximo passo, será apresentada uma forma de obter uma cópia coerente antes de usar o valor no restante do loop().

Passo 4 de 8
Uma flag de um byte admite acessos simples, mas consultar e limpar em seguida é uma operação composta sujeita à intercalação da ISR.
Uma flag como eventoPendente pode ocupar 1 byte na SRAM do ATmega328P. Ler esse byte ou escrever nele é um acesso simples. Mas uma decisão como “se estiver ativa, faça algo e depois limpe” contém vários passos separados.
Entre a consulta e a limpeza, a ISR pode executar e sinalizar a flag outra vez. Portanto, o tamanho de 1 byte evita o problema de uma leitura multibyte incoerente, mas não torna a sequência inteira indivisível.
Trecho analisado: a ISR registra uma ocorrência, enquanto o loop responde a ela.
volatile bool eventoPendente = false;
ISR(TIMER1_COMPA_vect) {
eventoPendente = true;
}
void loop() {
if (eventoPendente) {
// Executa a resposta ao evento.
eventoPendente = false;
}
}
A nova sinalização da ISR pode cair exatamente entre a leitura feita pelo loop e sua escrita de limpeza.
Considere que eventoPendente já vale true porque houve uma comparação anterior do Timer1. Uma intercalação possível é:
if (eventoPendente) e lê true.eventoPendente = true.eventoPendente = false com base na decisão tomada no passo 1.No final, a flag fica false. A sinalização do passo 2 foi apagada sem ser percebida em uma passagem posterior do loop.
Dica
O resultado depende do instante da interrupção. Se a ISR sinalizasse depois da limpeza, a flag terminaria como true e continuaria pendente. Se sinalizasse antes da consulta, o loop veria a flag ativa normalmente.
A corrida está na janela entre a consulta e a limpeza. Proteger somente uma delas ainda deixaria a outra ponta da janela exposta. A solução precisa tornar a operação lógica completa — retirar o estado e limpá-lo — uma única região indivisível.
No código da tela anterior, em qual momento uma nova sinalização da ISR pode ser perdida?

Passo 5 de 8
Proteja somente a cópia de um contador compartilhado e use o valor local fora da seção crítica.
Um contador de 16 ou 32 bits alterado pela ISR pode ser copiado pelo loop() enquanto a ISR o atualiza. Como o ATmega328P é um processador de 8 bits, essa cópia pode reunir partes de dois valores, como visto no passo anterior.
A solução é uma seção crítica curta: durante a cópia, as interrupções ficam desabilitadas. Assim, a ISR não se intercala nessa operação. Ao sair da seção, o estado anterior das interrupções é restaurado.

A proteção cobre somente a transferência do contador compartilhado para a cópia local.
No AVR, inclua util/atomic.h e use ATOMIC_BLOCK(ATOMIC_RESTORESTATE).
ATOMIC_RESTORESTATE guarda como as interrupções estavam na entrada do bloco e restaura essa condição na saída. Portanto, se elas já estavam desabilitadas por outro motivo, o bloco não as habilita indevidamente ao terminar.
A ISR é a única escritora de ticks; o loop() apenas obtém uma cópia coerente.
Contador compartilhado e cópia protegida
#include <avr/interrupt.h>
#include <util/atomic.h>
volatile uint16_t ticks = 0;
ISR(TIMER1_COMPA_vect) {
ticks++;
}
void loop() {
uint16_t ticksLocal;
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
ticksLocal = ticks;
}
// Use ticksLocal daqui em diante.
if (ticksLocal >= 1000) {
// Decisões e comandos de saída ficam fora do bloco.
}
}Atenção
A seção crítica não congela o Timer1: o hardware continua contando. Ela apenas adia o atendimento de interrupções enquanto o bloco executa.
Por isso, não coloque cálculos longos, delay(), laços de espera ou comandos de saída dentro do bloco. Copie o dado e volte a permitir o atendimento o quanto antes.
Complete o cabeçalho e a região protegida. O objetivo é copiar ticks para ticksLocal sem permitir que a ISR atualize o contador no meio da cópia. A condição e o comando de saída devem continuar fora do bloco.
Preencha as lacunas do código.
volatile uint16_t ticks = 0;
void loop() {
uint16_t ticksLocal;
{
}
if (ticksLocal >= 1000) {
digitalWrite(LED_BUILTIN, HIGH);
}
}Considere este mesmo bloco: ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { ticksLocal = ticks; }.
Qual é o estado das interrupções logo após o bloco se elas estavam desabilitadas antes de sua entrada?
Escreva pelo menos 20 caracteres (0/20).
Se as interrupções estavam habilitadas ao entrar no mesmo bloco, elas estarão habilitadas após sua saída. Em ambos os casos, o bloco preserva a condição que encontrou.
A cópia local é um instantâneo coerente, não uma promessa de que o contador não mudará logo depois: a ISR pode executar assim que as interrupções voltarem a ser permitidas.

Passo 6 de 8
Faça a retirada atômica de uma flag para que uma sinalização posterior da ISR permaneça disponível, mantendo o processamento fora da seção crítica.
Uma flag compartilhada pode ser retirada sem perder uma sinalização que chegue depois da retirada. A ideia é curta:
As etapas 1 e 2 precisam formar uma única operação protegida. O trabalho em si fica fora do bloco atômico.

A seção crítica transfere o sinal pendente para uma cópia local; ela não deve conter o processamento demorado.
Considere eventoPendente escrita pela ISR e consultada por loop(). A cópia local não é compartilhada: depois de receber o valor, ela pode ser usada normalmente fora da região protegida.
Retirada atômica de uma flag sinalizada pela ISR.
#include <util/atomic.h>
volatile bool eventoPendente = false;
ISR(TIMER1_COMPA_vect) {
eventoPendente = true;
}
void loop() {
bool atenderEvento;
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
atenderEvento = eventoPendente;
eventoPendente = false;
}
if (atenderEvento) {
// Faça aqui o trabalho associado ao evento.
}
}Dica
ATOMIC_RESTORESTATE devolve as interrupções ao estado que tinham antes do bloco. Portanto, não habilite interrupções incondicionalmente após essa retirada.
Complete as duas instruções que fazem a retirada da flag. A primeira guarda o sinal para esta passagem do loop(). A segunda libera a flag compartilhada para receber uma nova sinalização.
Preencha a retirada protegida da flag.
bool atenderEvento;
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
atenderEvento = ;
eventoPendente = ;
}
if (atenderEvento) {
// processar fora da seção crítica
}Rastreie esta sequência. Inicialmente, eventoPendente vale true.
true para atenderEvento e limpa eventoPendente para false.true em eventoPendente.atenderEvento é true.Organize as consequências na ordem em que acontecem.
Ordene os passos após a retirada inicial da flag.
Atenção
Essa técnica preserva apenas a informação “houve ao menos uma ocorrência”. Se a ISR sinalizar várias vezes enquanto a flag já estiver em true, a flag continua sendo somente true; ela não informa quantas ocorrências houve.

Passo 7 de 8
Selecione entre flag e contador conforme a informação que precisa sobreviver entre a ISR e o loop, transferindo contagens em lote com proteção curta.
Uma flag responde a: “houve pelo menos uma ocorrência desde a última retirada?”. Se a ISR executar várias vezes enquanto a flag já vale true, a informação continua sendo apenas “sim”.
Um contador responde a: “quantos atendimentos da ISR ocorreram?”. Cada atendimento incrementa o valor, até o limite do tipo. Escolha pelo dado que o loop realmente precisa preservar.

A flag agrega ocorrências; o contador preserva a quantidade de atendimentos, dentro da faixa disponível.
Exemplo
Use uma flag quando basta solicitar uma atualização pendente — por exemplo, recalcular uma saída uma vez no loop. Use um contador quando cada atendimento precisar entrar no processamento, como acumular quantos intervalos foram atendidos.
Compare o que cada representação mantém enquanto o loop ainda não consumiu o dado.
| Necessidade do loop | Representação | Informação preservada |
|---|---|---|
| Há trabalho pendente? | Flag | Pelo menos uma ocorrência |
| Processar cada atendimento? | Contador | Quantidade de atendimentos |
| Três sinais chegam com flag ativa | Flag | Continua apenas ativa |
| Três atendimentos incrementam | Contador | Valor aumenta em três |
Para consumir um lote, copie o contador compartilhado e zere-o no mesmo bloco atômico. Assim, o lote retirado fica em lote; um incremento da ISR depois do bloco permanece em atendimentos para uma passagem futura do loop().
O processamento do lote — decisões, cálculos e comandos de saída — fica fora do bloco. A região protegida só transfere a informação compartilhada.
Exemplo
Neste padrão, a ISR é a única escritora por incremento; o loop transfere o valor acumulado e o reinicia dentro da região curta.
Contador compartilhado incrementado pela ISR e lote local consumido pelo loop.
#include <util/atomic.h>
volatile uint16_t atendimentos = 0;
ISR(TIMER1_COMPA_vect) {
atendimentos++;
}
void loop() {
uint16_t lote;
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
lote = atendimentos;
atendimentos = 0;
}
if (lote > 0) {
// Processa o lote aqui, fora da seção crítica.
}
}Atenção
O contador não é infinito. Se ele alcançar o maior valor do tipo e receber outro incremento, ocorre retorno a zero. Um tipo mais largo adia esse problema, mas não elimina a necessidade de o loop consumir a contagem a tempo.
Defina uma capacidade coerente com a frequência de atendimentos e com o maior tempo previsto até a retirada.
Há ainda dois níveis diferentes de informação:
Uma pendência de comparação no hardware é uma indicação, não uma fila. Se novas comparações ocorrerem enquanto uma pendência já está aguardando atendimento, elas podem se agregar. Portanto, contar execuções da ISR não recupera automaticamente todas as comparações físicas que não ficaram registradas separadamente.

O software conta atendimentos que ocorreram; ele não cria registros separados para eventos físicos que o hardware já agregou.
Associe cada requisito à representação mais adequada.
Toque em um item e depois no par correspondente.
Complete a retirada atômica do lote de atendimentos.
uint16_t lote;
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
= atendimentos;
= 0;
}
if (lote > 0) {
// Processamento fora do bloco
}
Passo 8 de 8
Conclua uma solução em que a ISR registra atendimentos do Timer1, o loop transfere a contagem com proteção e processa o resultado fora da seção crítica.
Neste firmware, o Timer1 gera atendimentos periódicos. A ISR registra cada atendimento com rapidez; o loop() transfere a quantidade acumulada e decide o estado do LED depois que as interrupções foram restauradas.
Mapa de acessos de atendimentos:
atendimentos.atendimentos ao copiar o lote e escreve 0 ao concluir a transferência.lote e ledAceso: pertencem somente ao loop().Como a ISR pode modificar atendimentos fora da sequência normal do loop(), ele é volatile. Como a cópia e a zeragem são uma operação composta, ambas ficam na mesma seção crítica. O contador é apropriado porque a aplicação preserva quantos atendimentos da ISR ocorreram, e não apenas se houve algum.

A seção crítica transfere o lote: copia e zera o contador. O processamento e o comando do LED ficam fora dela.
Atenção
uint16_t comporta valores de 0 a 65535. O loop() precisa retirar os lotes com frequência suficiente para que atendimentos não retorne a zero por excesso de incrementos.
O contador representa atendimentos da ISR, não necessariamente todas as comparações físicas do periférico. Se várias comparações se acumularem em uma única pendência de hardware antes do atendimento, elas podem já ter sido agregadas antes de a ISR executar.
Prática opcional no Uno R3: crie um sketch, grave-o na placa alimentada somente pelo USB e observe o LED integrado. A ISR só incrementa o contador. O loop() retira o lote protegido, aplica as alternâncias necessárias e comanda o LED fora da seção crítica.
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/atomic.h>
volatile uint16_t atendimentos = 0;
ISR(TIMER1_COMPA_vect) {
atendimentos++;
}
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 0;
OCR1A = 31249;
TCCR1B |= _BV(WGM12);
TIFR1 = _BV(OCF1A);
TIMSK1 |= _BV(OCIE1A);
TCCR1B |= _BV(CS12);
}
void loop() {
uint16_t lote;
static bool ledAceso = false;
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
lote = atendimentos;
atendimentos = 0;
}
if (lote > 0) {
if ((lote & 1U) != 0U) {
ledAceso = !ledAceso;
}
digitalWrite(LED_BUILTIN, ledAceso ? HIGH : LOW);
}
}
LED integrado: alterna aproximadamente a cada 0,5 s. Nenhuma mensagem é exibida no monitor serial.
O resultado é físico, não uma saída no monitor serial. Se o loop estiver sendo atendido normalmente, cada comparação gera uma alternância. Um lote maior também é processado fora da seção crítica.
Dica
O Timer1 foi configurado para comparação a cada aproximadamente 0,5 s. Em condições normais, o loop() retira um atendimento por vez e o LED alterna nesse ritmo.
A aparência regular do LED é uma observação funcional, mas não prova por si só que não existe corrida. A justificativa está na sequência de acessos: cópia e zeragem ocorrem juntas no bloco atômico; cálculos e escrita no LED acontecem depois dele.
Complete a transferência do contador compartilhado para o lote local. A seção crítica deve conter apenas a transferência; o processamento continua fora dela.
uint16_t lote;
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
= ;
atendimentos = ;
}
if (lote > 0) {
// Processar o lote fora da seção crítica.
}Explique por que este firmware usa um contador, e não uma flag, para atendimentos. Em seguida, rastreie a sequência: o contador vale 3; o loop entra no bloco, copia 3 para lote e zera atendimentos; logo depois de sair do bloco, a ISR executa uma vez.
Qual lote é processado agora e qual valor fica disponível para a próxima passagem?
Escreva pelo menos 90 caracteres (0/90).
Resumo
atendimentos e o loop o zera durante a transferência.volatile exige os acessos previstos ao dado compartilhado, mas não torna uma sequência atômica.atendimentos e zere-o no mesmo ATOMIC_BLOCK(ATOMIC_RESTORESTATE).Parabéns! Você concluiu: Dados compartilhados entre interrupções e loop
Milhares de cursos online em vídeo, ebooks e áudiobooks.
Para testar seus conhecimentos no decorrer dos cursos online
Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail
Baixe nosso aplicativo pelo QR Code ou pelos links abaixo:.
+ de 10 milhões
de alunos
Certificado grátis e
válido em todo o Brasil
60 mil exercícios
gratuitos
4,8/5 classificação
nas lojas de apps
Cursos gratuitos em
vídeo, ebooks e audiobooks