Bem-vindo ao oitavo capítulo da Especialização em Engenharia de Sistemas com Linguagem C (C17/C23)! ⚡

À medida que os sistemas computacionais crescem em complexidade, manter todo o código em um único bloco contínuo torna-se inviável. A Modularização através de Funções permite dividir problemas complexos em sub-rotinas especializadas, reutilizáveis, testáveis e com fronteiras bem definidas de memória e escopo.

Modularização, Funções, Escopo e Linha de Comando argc/argv em C


🗺️ Mapa Conceitual do Capítulo

graph TD
    A["Modularização em C (C17/C23)"] --> B["1. Funções & Protótipos"]
    A --> C["2. Passagem por Valor"]
    A --> D["3. Escopo & Modificador static"]
    A --> E["4. Linha de Comando (argc/argv)"]

    B --> B1["Assinatura, Tipo de Retorno, Declaração prévia (.h)"]
    C --> C1["Cópia na Stack, Criação e destruição de Stack Frames"]
    D --> D1["Escopo Local, Global e variáveis static persistentes (.data)"]
    E --> E1["Recepção de argumentos, conversão com strtol() e <errno.h>"]

🧩 1. Anatomia de uma Função e Declaração de Protótipos

Uma função em C é composta por quatro elementos essenciais:

  1. Tipo de Retorno: O tipo de dado que a função devolve ao chamador (ex: int, double, ou void se nada for retornado).
  2. Nome / Identificador: Nome único que segue as regras de identificadores em C.
  3. Lista de Parâmetros Formais: Tipos e nomes das entradas esperadas.
  4. Corpo da Função: Bloco delimitado por { ... } contendo as instruções executáveis.

Por que Protótipos (Forward Declarations) são Obrigatórios?

O compilador C lê o código de cima para baixo. Se você chamar uma função antes de sua definição, o compilador emitirá um aviso (warning: implicit declaration of function). O protótipo informa a assinatura da função com antecedência:

#include <stdio.h>

// 1. Protótipo da Função (Declaração prévia)
double calcularAreaCirculo(double raio);

int main(void) {
    double area = calcularAreaCirculo(5.0); // Chamada válida
    printf("Area do circulo: %.2lf\n", area);
    return 0;
}

// 2. Implementação da Função
double calcularAreaCirculo(double raio) {
    const double PI = 3.141592653589793;
    return PI * raio * raio;
}

📦 2. Passagem de Parâmetros por Valor (Pass-by-Value)

[!IMPORTANT] Em C, todas as funções recebem parâmetros estritamente POR VALOR (Cópia). Quando uma função é chamada, a CPU cria um novo Stack Frame na memória e copia os valores dos argumentos. Modificar uma variável parâmetro dentro da função não altera a variável original do chamador:

void tentarAlterar(int x) {
    x = 999; // Altera apenas a cópia local no Stack Frame da função
}

int main(void) {
    int valor = 10;
    tentarAlterar(valor);
    printf("Valor: %d\n", valor); // Continua valendo 10!
    return 0;
}

🌐 3. Escopo de Variáveis e o Modificador static

Em C, o escopo define onde uma variável é visível e quanto tempo ela sobrevive na memória:

graph TD
    S["Tipos de Escopo e Armazenamento"] --> S1["1. Escopo Local / Bloco (Stack)\nCriada ao entrar no bloco, destruída ao sair."]
    S --> S2["2. Escopo Global (Data/BSS)\nVisível no arquivo todo. Evite o uso!"]
    S --> S3["3. Variável Local static (Data)\nPreserva seu valor entre chamadas sucessivas!"]

    style S fill:#1e293b,stroke:#10b981,color:#ffffff
    style S1 fill:#0f172a,stroke:#38bdf8,color:#e2e8f0
    style S2 fill:#0f172a,stroke:#ef4444,color:#e2e8f0
    style S3 fill:#0f172a,stroke:#10b981,color:#e2e8f0

Exemplo do Poder de static Local:

void registrarLog(void) {
    // Alocada na seção .data (não na Stack). Inicializada apenas 1 vez!
    static uint32_t contadorChamadas = 0;
    contadorChamadas++;
    printf("Log registrado. Total de chamadas: %u\n", contadorChamadas);
}

💻 4. Argumentos de Linha de Comando: int main(int argc, char *argv[])

A função principal pode receber parâmetros diretamente do terminal do sistema operacional:

  • argc (Argument Count): Quantidade total de argumentos passados na linha de comando (incluindo o próprio nome do programa, logo sempre $\ge 1$).
  • argv (Argument Vector): Array de ponteiros para strings (char*) contendo o texto de cada argumento.
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    if (argc < 3) {
        fprintf(stderr, "Uso correto: %s <host> <porta>\n", argv[0]);
        return EXIT_FAILURE; // 1
    }

    char *host = argv[1];
    int porta = atoi(argv[2]); // Converte string para inteiro

    printf("Conectando ao host %s na porta %d...\n", host, porta);
    return EXIT_SUCCESS; // 0
}

🛡️ 5. Tratamento de Erros do Sistema com <errno.h>

Quando chamadas de sistema ou funções da biblioteca padrão falham, a biblioteca <errno.h> armazena um código numérico de falha na variável global errno:

#include <stdio.h>
#include <errno.h>
#include <string.h>

void testarArquivo(const char *caminho) {
    FILE *f = fopen(caminho, "r");
    if (f == NULL) {
        // perror imprime a mensagem formatada associada ao errno atual
        perror("[FALHA NO ARQUIVO]");
        // Ou usando strerror(errno):
        fprintf(stderr, "Codigo do Erro: %d (%s)\n", errno, strerror(errno));
    }
}

🔍 6. Diagnóstico & Resolução de Problemas (Troubleshooting)

Sintoma Observado Causa Provável Como Resolver
A variável não mantém o valor após o término da função Esperança de que uma variável local comum da Stack retenha estado. Adicione o modificador static na declaração da variável local.
Crash com Segmentation Fault ao rodar com argumentos Acesso a argv[1] ou argv[2] sem validar se argc é suficiente. Sempre valide if (argc < N) antes de acessar qualquer posição de argv.
Warning: implicit declaration of function A função foi chamada antes de ter seu protótipo declarado no topo do arquivo. Adicione a assinatura da função com ; antes da função main.

🏆 7. Desafio Prático de Consolidação

Enunciado do Desafio: Desenvolva uma ferramenta utilitária CLI em C chamada conversor_unidades_cli.c:

  1. O programa deve receber argumentos de linha de comando: ./conversor <valor> <unidade_origem> <unidade_destino>.
    • Exemplo: ./conversor 100.0 C F (converte 100°C para Fahrenheit).
    • Exemplo: ./conversor 50.0 KM MI (converte 50 km para Milhas).
  2. Valide se argc == 4. Se incorreto, exiba a sintaxe correta e encerre com EXIT_FAILURE.
  3. Crie funções dedicadas com protótipos para cada conversão:
    • double celsiusParaFahrenheit(double c);
    • double kmParaMilhas(double km);
  4. Utilize uma variável static dentro de uma função void registrarHistorico(double v1, double v2) para contabilizar quantas conversões o sistema realizou.
🔍 Ver Solução Comentada do Desafio
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

static int execucoesGlobais = 0; // Escopo de arquivo privado

bool processarOperacao(double a, double b, char op, double *resultado) {
    execucoesGlobais++;
    switch (op) {
        case '+': *resultado = a + b; return true;
        case '-': *resultado = a - b; return true;
        case '*': *resultado = a * b; return true;
        case '/': 
            if (b == 0.0) return false;
            *resultado = a / b;
            return true;
        default: return false;
    }
}

int main(int argc, char *argv[]) {
    if (argc != 4) {
        printf("Uso: %s <num1> <op [+|-|*|/]> <num2>\n", argv[0]);
        return 1;
    }

    double n1 = atof(argv[1]);
    char op = argv[2][0];
    double n2 = atof(argv[3]);
    double res;

    if (processarOperacao(n1, n2, op, &res)) {
        printf(">> Resultado: %.4lf (Chamada #%d)\n", res, execucoesGlobais);
    } else {
        printf(">> [ERRO]: Operacao invalida ou divisao por zero!\n");
    }

    return 0;
}

🧭 Navegação Rápida

| 📖 Teoria | 📊 Slides | 🧠 Quiz | 💻 Exemplos | 🧩 Exercícios | | :— | :— | :— | :— | :— | | Ler Teoria | Ver Slides | Fazer Quiz | Ver Exemplos | Praticar Exercícios |


🧭 Navegação do Capítulo: ⬅️ Capítulo Anterior · 📚 Sumário do Módulo · ➡️ Próximo Capítulo