Atualizado: 16/11/2025

Este conteúdo é original e não foi gerado por inteligência artificial.

Macros no Pré-processador do Assembly NASM

Assim como as funções, as macros são um mecanismo para organizar a funcionalidade em blocos de código reutilizáveis. Uma macro é essencialmente um rótulo associado a um trecho de código. Quando o pré-processador do NASM encontra o nome de uma macro no código fonte, ele o substitui pelo código contido dentro da macro. Esse processo é chamado de expansão de macro.

As macros são ideais para substituir sequências de código longas e repetitivas por chamadas curtas e legíveis.

A definição de uma macro tem a seguinte forma geral:

%macro nome_da_macro numero_de_parametros
    ; código da macro
%endmacro

A definição começa com a diretiva %macro, seguida pelo nome da macro e o número de parâmetros que ela aceita. O bloco de código é delimitado pela diretiva de fechamento %endmacro.

Macro Simples sem Parâmetros

Vamos considerar um exemplo de macro que imprime uma string no console em um programa para Linux.

global _start

section .data
    msg db "Hello programicio.com", 10
    msglen equ $-msg

section .text

; Macro que imprime uma string fixa no console
%macro Print 0
    mov rax, 1          ; Chamada de sistema (write)
    mov rdi, 1          ; Descritor de arquivo (stdout)
    mov rsi, msg        ; Endereço da string
    mov rdx, msglen     ; Comprimento da string
    syscall
%endmacro

_start:
    Print               ; Expande a macro Print aqui

    mov rax, 60         ; Chamada de sistema (exit)
    xor rdi, rdi        ; Código de saída 0
    syscall

Neste caso, a macro se chama Print e não aceita parâmetros, indicado pelo 0. Para usar a macro, basta escrever seu nome. Após o pré-processamento, o código que o montador irá compilar será:

_start:
    mov rax, 1
    mov rdi, 1
    mov rsi, msg
    mov rdx, msglen
    syscall

    mov rax, 60
    xor rdi, rdi
    syscall

O código da macro é inserido diretamente no local da chamada. Uma grande vantagem é que podemos chamar a macro múltiplas vezes para repetir o código.

_start:
    Print       ; Primeira impressão
    Print       ; Segunda impressão
    Print       ; Terceira impressão

    ; Finaliza o programa corretamente
    mov rax, 60         ; Chamada de sistema (exit)
    xor rdi, rdi        ; Código de saída 0
    syscall

Isso resultará na expansão do código da macro três vezes, imprimindo a mensagem "Hello programicio.com" três vezes no console.

A seguir, um exemplo análogo de macro sem parâmetros para Windows.

global _start

extern WriteFile
extern GetStdHandle

section .data
    msg db "Hello programicio.com", 10
    msglen equ $-msg

section .text

; Macro para imprimir uma string fixa no console (Windows)
%macro Print 0
    sub  rsp, 40
    mov  rcx, -11
    call GetStdHandle
    mov  rcx, rax
    mov  rdx, msg
    mov  r8d, msglen
    xor  r9, r9
    mov  qword [rsp + 32], 0
    call WriteFile
    add  rsp, 40
%endmacro

_start:
    Print
    ret

Macros com Parâmetros

Macros se tornam muito mais flexíveis quando aceitam parâmetros. Isso permite que o mesmo bloco de código opere sobre dados diferentes. Vamos modificar a macro Print para que ela possa imprimir qualquer string.

global _start

section .data
    msg1 db "Hello programicio.com", 10
    msglen1 equ $-msg1
    msg2 db "Hello World", 10
    msglen2 equ $-msg2

section .text
; Macro que imprime uma string passada por parâmetro
%macro Print 2
    mov rax, 1
    mov rdi, 1
    mov rsi, %1     ; %1 representa o primeiro parâmetro
    mov rdx, %2     ; %2 representa o segundo parâmetro
    syscall
%endmacro

_start:
    Print msg1, msglen1 ; Passa o endereço de msg1 e seu tamanho
    Print msg2, msglen2 ; Passa o endereço de msg2 e seu tamanho

    mov rax, 60
    xor rdi, rdi
    syscall

Agora, a macro Print é definida para aceitar 2 parâmetros. Dentro da macro, esses parâmetros são acessados pelos placeholders %1, %2, e assim por diante. Ao chamar a macro, os valores (argumentos) são passados posicionalmente: msg1 é associado a %1, e msglen1 a %2.

Este programa imprimirá as duas strings diferentes no console.

$ nasm -f elf64 hello.asm -o hello.o
$ ld hello.o -o hello
$ ./hello
Hello programicio.com
Hello World

Rótulos em Macros

Macros podem conter rótulos, mas isso apresenta um problema: se a macro for expandida várias vezes, o mesmo rótulo será definido múltiplas vezes, causando um erro de "redefinição de rótulo" durante a montagem.

; Exemplo com erro
%macro Sum 2
    ; ...
    jmp condition
while:
    ; ...
condition:
    cmp rcx, 0
    jne while
%endmacro

_start:
    Sum arr1, arrlen1
    Sum arr2, arrlen2   ; ERRO: 'while' e 'condition' serão redefinidos

Para resolver isso, o NASM oferece rótulos locais de macro, que são prefixados com %%. O NASM garante que cada expansão da macro gere um nome de rótulo único.

global _start

section .data
    size equ 8
    arr1 dq 1, 2, 3, 4
    arrlen1 equ ($-arr1)/size
    arr2 dq 5, 6, 7, 8
    arrlen2 equ ($-arr2)/size

section .text
; Macro para somar os elementos de um array
%macro Sum 2
    mov rax, 0
    mov rdi, %1
    mov rcx, %2
    jmp %%condition     ; Salta para o rótulo local
%%while:                ; Definição do rótulo local
    dec rcx
    add rax, [rdi+rcx*size]
%%condition:
    cmp rcx, 0
    jne %%while
%endmacro

_start:
    Sum arr1, arrlen1
    mov rdi, rax
    Sum arr2, arrlen2
    add rdi, rax

    mov rax, 60
    syscall

Agora, os rótulos %%while e %%condition são locais para cada expansão da macro. O NASM os converterá internamente para nomes únicos (como ..@1771.while), evitando conflitos.

Macros ou Funções?

A escolha entre usar uma macro ou uma função (procedimento) envolve um trade-off:

  • Macros:

    • Vantagem: Mais rápidas. O código é inserido diretamente (inlining), evitando a sobrecarga de uma chamada de função (call/ret). O código pode se tornar mais legível.
    • Desvantagem: Aumentam o tamanho do executável final, pois o código é duplicado em cada local de chamada.
  • Funções:

    • Vantagem: Reduzem o tamanho do executável, pois o código existe em um único local.
    • Desvantagem: Um pouco mais lentas devido à sobrecarga das instruções call e ret e à necessidade de gerenciar a pilha.

Recomendação geral: use macros para trechos de código curtos e críticos em termos de desempenho. Use funções para blocos de código mais longos ou quando o tamanho do programa é uma preocupação maior que a velocidade.


Resumo

  • Macros são blocos de código reutilizáveis que são expandidos pelo pré-processador diretamente no local da chamada.
  • Elas podem ser definidas sem parâmetros ou com múltiplos parâmetros, acessados por %1, %2, etc.
  • Para evitar conflitos de nomes, rótulos dentro de macros devem ser locais, prefixados com %%.
  • Macros oferecem melhor desempenho que funções ao custo de um executável maior.
Política de Privacidade

Copyright © www.programicio.com Todos os direitos reservados

É proibida a reprodução do conteúdo desta página sem autorização prévia do autor.

Contato: programicio@gmail.com