Computer Science › Como os Computadores Realmente Funcionam
16 registradores de uso geral de 64 bits, cada um com sub-porções endereçáveis de 32, 16 e 8 bits, entre os quais se move dados com `mov` e suas variantes com extensão de zeros ou de sinal. É o mesmo banco de registradores já construído com flip-flops, agora com seus nomes reais do x86-64.
`add`, `sub`, `imul`, `and`, `or`, `xor` e os deslocamentos operam diretamente sobre registradores ou memória, e `lea` (load effective address) calcula uma expressão aritmética de endereço sem nunca tocar a memória para a qual ela aponta.
`cmp` e `test` ligam silenciosamente as flags de zero, sinal, overflow e carry; `jcc`, `setcc` e `cmovcc` são as instruções que de fato as leem de volta. São exatamente as flags da ULA já vistas em hardware, agora conduzindo um controle de fluxo real.
Um compilador reduz todo `if`, `while` e `for` ao mesmo pequeno vocabulário de comparações e saltos condicionais, a tradução mecânica que o ACM/IEEE CS2013 nomeia diretamente como um resultado de aprendizagem central para a linguagem assembly.
Todo programa em execução enxerga seu próprio layout privado de memória (código, globais inicializadas e não inicializadas, um heap que cresce para cima e uma pilha que cresce para baixo), uma visão que o sistema operacional constrói, e não a RAM física bruta que a máquina de fato tem.
Uma região de memória LIFO que guarda as variáveis locais e a contabilidade de chamadas de toda função, crescendo para baixo um frame por vez e encolhendo automaticamente no instante em que uma função retorna. É exatamente a estrutura de hardware que deu nome ao TAD Pilha.
`call` empilha o endereço de retorno e salta para a função chamada; `ret` desempilha esse mesmo endereço de volta para o ponteiro de instrução. É todo o mecanismo que um procedimento usa para devolver o controle a quem quer que o tenha invocado.
Uma sequência curta e fixa de instruções, o prólogo, reserva um frame para as variáveis locais de uma função na entrada, e um epílogo correspondente o desmonta antes de retornar, deixando a pilha exatamente como quem chamou a deixou.
O acordo exato e obrigatório que os compiladores no Linux/macOS seguem para que funções compiladas de forma independente consigam chamar umas às outras corretamente, definindo quais registradores levam os seis primeiros argumentos, qual leva o valor de retorno, quais uma função chamada pode sobrescrever livremente e como a pilha precisa ficar alinhada em cada chamada.
Uma pequena função recursiva em C compilada até o assembly x86-64 real e depois rastreada chamada por chamada, com todo ponteiro, stack frame e regra de convenção de chamada vistos nesta disciplina funcionando juntos para fazer a recursão, aceita por fé desde Programação e Pensamento Computacional, acontecer fisicamente.
Um ponteiro é uma variável comum cujo valor por acaso é um endereço de memória. `&` pede a um objeto o seu endereço, `*` segue um endereço de volta até o objeto que mora lá, e todo acesso indireto em C se reduz a só esses dois operadores.
Memória pedida explicitamente em tempo de execução com `malloc` e liberada explicitamente com `free`, para dados cujo tamanho ou tempo de vida não pode ser preso a nenhuma chamada de função isolada. É o heap real para o qual a realocação por duplicação de um array dinâmico de fato recorre.
O gerenciamento manual de memória falha de três formas distintas e bem conhecidas (esquecer de liberar, que é um vazamento; liberar duas vezes, que é um double free; e usar memória depois de liberada, que é um ponteiro pendente / use-after-free), cada uma com seu próprio sintoma e sua própria correção.
Um ponteiro sem tipo, que pode apontar para qualquer coisa, mas não pode ser desreferenciado como nada até receber uma conversão (cast). É o truque que `malloc`, `qsort` e `memcpy` usam para continuar genéricos numa linguagem sem templates.
Um ponteiro cujo alvo é código executável, e não dados. É o mecanismo por trás de callbacks e tabelas de despacho numa linguagem que não tem funções de primeira classe próprias.
Uma struct dispõe seus campos de forma contígua na ordem de declaração, com padding inserido pelo compilador para manter cada campo alinhado; uma union sobrepõe todos os membros no mesmo endereço inicial, um de cada vez.
Somar 1 a um ponteiro o avança um elemento inteiro, e não um byte (a aritmética é escalada pelo tamanho do alvo), e em quase toda expressão o nome de um array decai silenciosamente para um ponteiro para o seu primeiro elemento.
Escrever além do fim de um buffer alocado na pilha não corrompe só uma variável vizinha. Levado longe o bastante, sobrescreve o próprio endereço de retorno salvo, o clássico primeiro passo para sequestrar o fluxo de controle de um programa.
Part of the Computer Science curriculum.
16 registradores de uso geral de 64 bits, cada um com sub-porções endereçáveis de 32, 16 e 8 bits, entre os quais se move dados com `mov` e suas variantes com extensão de zeros ou de sinal. É o mesmo banco de registradores já construído com flip-flops, agora com seus nomes reais do x86-64.
`add`, `sub`, `imul`, `and`, `or`, `xor` e os deslocamentos operam diretamente sobre registradores ou memória, e `lea` (load effective address) calcula uma expressão aritmética de endereço sem nunca tocar a memória para a qual ela aponta.
`cmp` e `test` ligam silenciosamente as flags de zero, sinal, overflow e carry; `jcc`, `setcc` e `cmovcc` são as instruções que de fato as leem de volta. São exatamente as flags da ULA já vistas em hardware, agora conduzindo um controle de fluxo real.
Um compilador reduz todo `if`, `while` e `for` ao mesmo pequeno vocabulário de comparações e saltos condicionais, a tradução mecânica que o ACM/IEEE CS2013 nomeia diretamente como um resultado de aprendizagem central para a linguagem assembly.
Todo programa em execução enxerga seu próprio layout privado de memória (código, globais inicializadas e não inicializadas, um heap que cresce para cima e uma pilha que cresce para baixo), uma visão que o sistema operacional constrói, e não a RAM física bruta que a máquina de fato tem.
Uma região de memória LIFO que guarda as variáveis locais e a contabilidade de chamadas de toda função, crescendo para baixo um frame por vez e encolhendo automaticamente no instante em que uma função retorna. É exatamente a estrutura de hardware que deu nome ao TAD Pilha.
`call` empilha o endereço de retorno e salta para a função chamada; `ret` desempilha esse mesmo endereço de volta para o ponteiro de instrução. É todo o mecanismo que um procedimento usa para devolver o controle a quem quer que o tenha invocado.
Uma sequência curta e fixa de instruções, o prólogo, reserva um frame para as variáveis locais de uma função na entrada, e um epílogo correspondente o desmonta antes de retornar, deixando a pilha exatamente como quem chamou a deixou.
O acordo exato e obrigatório que os compiladores no Linux/macOS seguem para que funções compiladas de forma independente consigam chamar umas às outras corretamente, definindo quais registradores levam os seis primeiros argumentos, qual leva o valor de retorno, quais uma função chamada pode sobrescrever livremente e como a pilha precisa ficar alinhada em cada chamada.
Uma pequena função recursiva em C compilada até o assembly x86-64 real e depois rastreada chamada por chamada, com todo ponteiro, stack frame e regra de convenção de chamada vistos nesta disciplina funcionando juntos para fazer a recursão, aceita por fé desde Programação e Pensamento Computacional, acontecer fisicamente.
Um ponteiro é uma variável comum cujo valor por acaso é um endereço de memória. `&` pede a um objeto o seu endereço, `*` segue um endereço de volta até o objeto que mora lá, e todo acesso indireto em C se reduz a só esses dois operadores.
Memória pedida explicitamente em tempo de execução com `malloc` e liberada explicitamente com `free`, para dados cujo tamanho ou tempo de vida não pode ser preso a nenhuma chamada de função isolada. É o heap real para o qual a realocação por duplicação de um array dinâmico de fato recorre.
O gerenciamento manual de memória falha de três formas distintas e bem conhecidas (esquecer de liberar, que é um vazamento; liberar duas vezes, que é um double free; e usar memória depois de liberada, que é um ponteiro pendente / use-after-free), cada uma com seu próprio sintoma e sua própria correção.
Um ponteiro sem tipo, que pode apontar para qualquer coisa, mas não pode ser desreferenciado como nada até receber uma conversão (cast). É o truque que `malloc`, `qsort` e `memcpy` usam para continuar genéricos numa linguagem sem templates.
Um ponteiro cujo alvo é código executável, e não dados. É o mecanismo por trás de callbacks e tabelas de despacho numa linguagem que não tem funções de primeira classe próprias.
Uma struct dispõe seus campos de forma contígua na ordem de declaração, com padding inserido pelo compilador para manter cada campo alinhado; uma union sobrepõe todos os membros no mesmo endereço inicial, um de cada vez.
Somar 1 a um ponteiro o avança um elemento inteiro, e não um byte (a aritmética é escalada pelo tamanho do alvo), e em quase toda expressão o nome de um array decai silenciosamente para um ponteiro para o seu primeiro elemento.
Escrever além do fim de um buffer alocado na pilha não corrompe só uma variável vizinha. Levado longe o bastante, sobrescreve o próprio endereço de retorno salvo, o clássico primeiro passo para sequestrar o fluxo de controle de um programa.