C Bibliotecas (.a e .so)

Uma biblioteca é um pacote de funções já compiladas, pronto para ser usado por outros programas. Você usa bibliotecas desde o primeiro printf, que mora na biblioteca C do sistema, e desde o primeiro -lm, que liga a biblioteca de matemática. Nesta aula, a Ana transforma o código da lojinha numa biblioteca dela, nos dois formatos do Linux: a estática (.a), copiada para dentro do programa, e a compartilhada (.so), carregada quando o programa roda.

Você vai aprender
  • O que são libc e libm, e como o ldd mostra as bibliotecas de um programa.
  • A criar uma biblioteca estática com ar rcs libloja.a.
  • A ligar com -I (onde estão os .h), -L (onde está a biblioteca) e -l (qual biblioteca), e por que a ordem no comando importa.
  • A criar uma biblioteca compartilhada com -fPIC e -shared.
  • A fazer o programa achar a .so: LD_LIBRARY_PATH e -Wl,-rpath.
  • Os erros reais: não foi possível localizar -lloja, error while loading shared libraries e recompilar com -fPIC.

Esta aula usa o que você viu em Vários Arquivos (gcc -c, arquivos .o, nm) e em static e extern. Todos os comandos rodaram na máquina fictícia da Ana, na pasta ~/c/biblio, com o gcc 15.2 e a glibc 2.43. O ligador (ld) fala português no sistema dela; o inglês vai citado ao lado. O editor do guia não cria bibliotecas (lá, todas as abas são compiladas e ligadas juntas a cada Rodar), mas você vai praticar nele o código e os erros de uma.


A ideia: a fotocópia e o empréstimo

A Ana precisa de três capítulos de um livro da biblioteca da escola. Ela tem dois jeitos de usá-los:

  • Tirar fotocópia e colar no caderno. O caderno fica um pouco mais grosso, mas funciona em qualquer lugar, mesmo se a biblioteca fechar. Se o livro ganhar uma edição corrigida, a cópia dela continua com os erros antigos.
  • Pegar emprestado toda vez que precisa. O caderno fica fino, e ela sempre lê a edição mais nova. Mas, se o livro não estiver na estante, ela fica sem.
Estática (.a): a fotocópiaCompartilhada (.so): o empréstimo
Quando o código entra no programaNa ligação: o ligador copia as funções para dentro do executávelQuando o programa roda: o carregador do sistema abre a .so
Depois de ligar, a biblioteca precisa existir?NãoSim, em todo computador onde o programa rodar
Corrigir um erro na bibliotecaLigar o programa de novoTrocar a .so; o programa usa a nova na próxima vez que rodar
Nome do arquivolibnome.a (de archive, "arquivo")libnome.so (de shared object, "objeto compartilhado")

No Windows, os nomes são outros (.lib e .dll), mas a ideia é a mesma.


1. Você já usa bibliotecas

Este programa usa duas bibliotecas: a biblioteca C (onde moram o printf e o resto do stdio.h) e a de matemática (o sqrt):

#include <stdio.h>
#include <math.h>

int main(void) {
    double area = 49.0;
    printf("Lado do quadrado: %.1f m\n", sqrt(area));
    return 0;
}
$ gcc -std=c23 -Wall -Wextra raiz.c -o raiz -lm
$ ./raiz
Lado do quadrado: 7.0 m
$ ldd ./raiz
	linux-vdso.so.1 (0x00007f26fc6ff000)
	libm.so.6 => /usr/lib/x86_64-linux-gnu/libm.so.6 (0x00007f26fc5cb000)
	libc.so.6 => /usr/lib/x86_64-linux-gnu/libc.so.6 (0x00007f26fc200000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f26fc701000)

O ldd (de list dynamic dependencies, "listar as dependências dinâmicas") mostra as bibliotecas compartilhadas de que um programa precisa para rodar, e onde o sistema as achou:

libm.so.6 => /usr/lib/…/libm.so.6
A biblioteca de matemática, que entrou por causa do -lm. O .6 no fim é o número da versão.
libc.so.6
A biblioteca C (no Linux, a glibc). Todo programa C a recebe sem precisar de -l.
/lib64/ld-linux-x86-64.so.2
O carregador (dynamic loader): o programa do sistema que, a cada ./raiz, acha e abre as .so antes do main começar.
linux-vdso.so.1
Um pedacinho do próprio núcleo do Linux, que todo programa recebe. Não é um arquivo; pode ignorar.
(0x00007f…)
O endereço da memória onde cada biblioteca foi carregada. Muda a cada execução.

Os arquivos da biblioteca de matemática ficam numa pasta do sistema:

$ ls /usr/lib/x86_64-linux-gnu/libm.*
/usr/lib/x86_64-linux-gnu/libm.a
/usr/lib/x86_64-linux-gnu/libm.so
/usr/lib/x86_64-linux-gnu/libm.so.6

Repare no padrão dos nomes: lib + m + .a ou .so. A opção -lm quer dizer "procure a biblioteca libm": o gcc acrescenta o lib na frente e o .so (ou .a) no fim. Para uma biblioteca chamada libloja, a opção será -lloja.


2. O projeto: a biblioteca da lojinha

A Ana quer usar as contas da lojinha em vários programas (o caixa, o site, a planilha de estoque). Ela organizou o código numa pasta com o cabeçalho em include/ e as implementações em src/, como fazem as bibliotecas de verdade:

$ ls -R
.:
include
main.c
src

./include:
loja.h

./src:
frete.c
total.c
#ifndef LOJA_H
#define LOJA_H

const char *loja_versao(void);
int loja_total(int preco, int quantidade);
int loja_frete(int km);

#endif
#include "loja.h"

const char *loja_versao(void) {
    return "1.0";
}

int loja_total(int preco, int quantidade) {
    return preco * quantidade;
}
#include "loja.h"

int loja_frete(int km) {
    if (km <= 10) {
        return 0;
    }
    return 5 + (km - 10);
}

O frete é grátis até 10 km; depois, custa R$ 5 mais R$ 1 por quilômetro a mais. Todas as funções públicas começam com loja_: como o C não tem "sobrenome" para funções, o prefixo evita que elas briguem com funções de mesmo nome no programa de quem usa a biblioteca (você vai ver essa briga na seção 3). O programa de teste:

#include <stdio.h>
#include "loja.h"

int main(void) {
    printf("Biblioteca da loja, versão %s\n", loja_versao());
    printf("3 cadernos: R$ %d\n", loja_total(12, 3));
    printf("Frete (8 km): R$ %d\n", loja_frete(8));
    printf("Frete (23 km): R$ %d\n", loja_frete(23));
    return 0;
}

-I: onde estão os .h

O main.c está numa pasta, e o loja.h em outra. O #include "loja.h" procura na pasta do próprio arquivo e não acha:

$ gcc -std=c23 -Wall -Wextra -c main.c
main.c:2:10: fatal error: loja.h: Arquivo ou diretório inexistente
    2 | #include "loja.h"
      |          ^~~~~~~~
compilation terminated.

A opção -I (de include) acrescenta uma pasta à busca dos .h (aula Pré-processador):

$ gcc -std=c23 -Wall -Wextra -Iinclude -c main.c
$ gcc -std=c23 -Wall -Wextra -Iinclude -c src/total.c src/frete.c
$ ls
frete.o
include
main.c
main.o
src
total.o

Os .o saem na pasta onde a Ana está (~/c/biblio), não em src/.


3. A biblioteca estática (.a)

Uma biblioteca estática é só uma caixa de arquivos .o, feita com o programa ar (de archiver, "arquivador"):

$ ar rcs libloja.a total.o frete.o
$ ar t libloja.a
total.o
frete.o
$ file libloja.a
libloja.a: current ar archive
ar rcs
Três letras de opções: r (replace) põe os .o na caixa, trocando os que já estiverem lá; c (create) cria a caixa sem reclamar se ela não existe; s grava um índice dos símbolos, que deixa a ligação mais rápida.
libloja.a
O nome da biblioteca: tem de começar com lib e terminar com .a para o -lloja achá-la.
ar t
Lista (table) o que há dentro.

O nm funciona com a caixa inteira, mostrando os símbolos de cada .o:

$ nm libloja.a

total.o:
0000000000000011 T loja_total
0000000000000000 T loja_versao

frete.o:
0000000000000000 T loja_frete

Ligar: -L e -l

$ gcc main.o -L. -lloja -o app
$ ./app
Biblioteca da loja, versão 1.0
3 cadernos: R$ 36
Frete (8 km): R$ 0
Frete (23 km): R$ 18
main.o
O seu programa. Vem antes das bibliotecas (veja abaixo por quê).
-L.
"Procure bibliotecas também nesta pasta" (. é a pasta atual). Sem espaço entre o -L e a pasta. As pastas do sistema, como /usr/lib/x86_64-linux-gnu, já estão na lista; por isso o -lm não precisa de -L.
-lloja
"Ligue a biblioteca libloja": o gcc procura libloja.so e libloja.a em cada pasta da lista.

O ligador copiou as três funções para dentro do app. O nm as encontra no executável, e o ldd não lista nenhuma libloja, porque o programa não precisa dela para rodar:

$ nm app | grep loja
000000000000120a T loja_frete
00000000000011f3 T loja_total
00000000000011e2 T loja_versao
$ ldd ./app
	linux-vdso.so.1 (0x00007f0174d62000)
	libc.so.6 => /usr/lib/x86_64-linux-gnu/libc.so.6 (0x00007f0174a00000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f0174d64000)
$ rm libloja.a
$ ./app
Biblioteca da loja, versão 1.0
3 cadernos: R$ 36
Frete (8 km): R$ 0
Frete (23 km): R$ 18

Mesmo com a libloja.a apagada, o programa roda: ele levou a fotocópia. (O | grep loja filtra a saída do nm e mostra só as linhas que contêm "loja".) Também dá para passar o arquivo direto, sem -L nem -l: gcc main.o libloja.a -o app.

Os dois erros de ligação mais comuns

Sem o -L., o ligador procura só nas pastas do sistema:

$ gcc main.o -lloja -o app
/usr/bin/x86_64-linux-gnu-ld.bfd: não foi possível localizar -lloja: Arquivo ou diretório inexistente
collect2: error: ld returned 1 exit status

(Em inglês: cannot find -lloja: No such file or directory.) E com a biblioteca antes do main.o:

$ gcc -L. -lloja main.o -o app
/usr/bin/x86_64-linux-gnu-ld.bfd: main.o: na função "main":
main.c:(.text+0x9): referência não definida para "loja_versao"
/usr/bin/x86_64-linux-gnu-ld.bfd: main.c:(.text+0x32): referência não definida para "loja_total"
/usr/bin/x86_64-linux-gnu-ld.bfd: main.c:(.text+0x54): referência não definida para "loja_frete"
/usr/bin/x86_64-linux-gnu-ld.bfd: main.c:(.text+0x76): referência não definida para "loja_frete"
collect2: error: ld returned 1 exit status

A biblioteca existe, e mesmo assim "referência não definida". O ligador lê o comando da esquerda para a direita. Quando chegou na libloja.a, ninguém ainda tinha pedido nenhuma loja_…, e ele não tirou nada da caixa. Depois veio o main.o, pedindo as três, mas a caixa já tinha ficado para trás. A regra: primeiro os seus arquivos, depois as bibliotecas. (É por isso que o -lm vai no fim do comando, e o LDLIBS no fim da receita do Makefile.)

Nomes internos da biblioteca

Uma biblioteca tem funções públicas (as do .h) e, muitas vezes, auxiliares. Na pasta ~/c/biblio2, a Ana pôs no frete.c uma auxiliar arredondar, que arredonda os quilômetros para o múltiplo de 5 de cima, e se esqueceu do static. Um programa que tem a sua própria arredondar (para notas) não liga:

$ gcc -std=c23 -Wall -Wextra -Iinclude main.c libloja.a -o app_a
/usr/bin/x86_64-linux-gnu-ld.bfd: libloja.a(frete.o): na função "arredondar":
frete.c:(.text+0x0): múltiplas definições de "arredondar"; /tmp/ccgh3i4y.o:main.c:(.text+0x0): definido primeiro aqui
collect2: error: ld returned 1 exit status

O libloja.a(frete.o) diz: o frete.o de dentro da libloja.a. O conserto é o da aula static e extern: tudo o que não está no .h da biblioteca leva static. Quem usa a biblioteca não pode tropeçar nos nomes internos dela.

Missão: no editor, a biblioteca é só mais uma aba, e o ligador junta tudo, como numa biblioteca estática. Rode e leia o erro (duplicate symbol: arredondar). Conserte só a aba frete.c, da biblioteca: a arredondar dela é um detalhe interno. O programa tem de mostrar Frete (23 km): R$ 20 (13 km a mais, arredondados para 15) e Nota 7 vira 10.

#include <stdio.h>
#include "loja.h"

int arredondar(int nota) {
    return nota >= 5 ? 10 : 0;
}

int main(void) {
    printf("Frete (23 km): R$ %d\n", loja_frete(23));
    printf("Nota 7 vira %d\n", arredondar(7));
    return 0;
}
#include "loja.h"

int arredondar(int km) {
    return (km + 4) / 5 * 5;
}

int loja_frete(int km) {
    if (km <= 10) {
        return 0;
    }
    return 5 + arredondar(km - 10);
}
#ifndef LOJA_H
#define LOJA_H

const char *loja_versao(void);
int loja_total(int preco, int quantidade);
int loja_frete(int km);

#endif
#include "loja.h"

static int arredondar(int km) {
    return (km + 4) / 5 * 5;
}

int loja_frete(int km) {
    if (km <= 10) {
        return 0;
    }
    return 5 + arredondar(km - 10);
}

4. A biblioteca compartilhada (.so)

A Ana volta ao frete.c sem a arredondar, na pasta ~/c/biblio. Uma .so é feita em dois passos: compilar os .o com -fPIC e juntá-los com -shared:

$ gcc -std=c23 -Wall -Wextra -Iinclude -fPIC -c src/total.c src/frete.c
$ gcc -shared total.o frete.o -o libloja.so
$ file libloja.so
libloja.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=438c9650823dbca86e8990b7667bfad3814ff9f3, not stripped
-fPIC
Position Independent Code, "código independente de posição". A .so vai ser carregada num endereço diferente em cada programa (você viu os endereços mudando no ldd), e o código tem de funcionar em qualquer um. Vai na etapa de compilar (-c).
-shared
"Gere uma biblioteca compartilhada em vez de um executável." Vai na etapa de ligar.
shared object
O que o file diz: um objeto compartilhado. Um executável apareceria como pie executable, e um .o como relocatable.

Para ver as funções que uma .so oferece, use nm -D (de dynamic):

$ nm -D libloja.so
                 w __cxa_finalize
                 w __gmon_start__
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
0000000000001121 T loja_frete
000000000000110a T loja_total
00000000000010f9 T loja_versao

As três T são as da Ana. As linhas com w (weak, "fraco") são ganchos que o gcc põe em toda .so; pode ignorar.

Ligar funciona; rodar, não

O comando de ligação é o mesmo da estática:

$ gcc -std=c23 -Wall -Wextra -Iinclude main.c -L. -lloja -o app
$ ./app
./app: error while loading shared libraries: libloja.so: cannot open shared object file: No such file or directory
$ echo $?
127

"Erro ao carregar as bibliotecas compartilhadas: não foi possível abrir o arquivo libloja.so." Esta mensagem é do carregador, não do gcc, e sai em inglês mesmo no sistema em português. O programa nem começou: o código de saída 127 é o mesmo de um comando que não existe.

O -L. só vale na hora de ligar: ele disse ao ligador onde achar a biblioteca para conferir os nomes. O programa gravou só o nome libloja.so. Na hora de rodar, o carregador procura esse nome nas pastas do sistema, e não na pasta atual. O ldd mostra o buraco:

$ ldd ./app
	linux-vdso.so.1 (0x00007f8271677000)
	libloja.so => not found
	libc.so.6 => /usr/lib/x86_64-linux-gnu/libc.so.6 (0x00007f8271400000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f8271679000)

Solução 1: LD_LIBRARY_PATH

A variável de ambiente LD_LIBRARY_PATH (aula Variáveis de Ambiente) acrescenta pastas à busca do carregador. Escrita antes do comando, ela vale só para ele:

$ LD_LIBRARY_PATH=. ./app
Biblioteca da loja, versão 1.0
3 cadernos: R$ 36
Frete (8 km): R$ 0
Frete (23 km): R$ 18
$ LD_LIBRARY_PATH=. ldd ./app
	linux-vdso.so.1 (0x00007efe23cfe000)
	libloja.so => ./libloja.so (0x00007efe23cee000)
	libc.so.6 => /usr/lib/x86_64-linux-gnu/libc.so.6 (0x00007efe23a00000)
	/lib64/ld-linux-x86-64.so.2 (0x00007efe23d00000)

Bom para testar, ruim para o dia a dia: você teria de lembrar dela toda vez.

Solução 2: gravar a pasta no programa (-Wl,-rpath)

$ gcc -std=c23 -Wall -Wextra -Iinclude main.c -L. -lloja -Wl,-rpath,'$ORIGIN' -o app
$ ./app | head -1
Biblioteca da loja, versão 1.0
$ ldd ./app
	linux-vdso.so.1 (0x00007f73c4df5000)
	libloja.so => /home/ana/c/biblio/libloja.so (0x00007f73c4de5000)
	libc.so.6 => /usr/lib/x86_64-linux-gnu/libc.so.6 (0x00007f73c4a00000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f73c4df7000)
$ readelf -d app | grep -i path
 0x000000000000001d (RUNPATH)            Biblioteca runpath: [$ORIGIN]
-Wl,…
"Passe isto ao ligador" (o l é de linker). O gcc não entende o -rpath; ele o repassa ao ld. As vírgulas separam as palavras: o ligador recebe -rpath $ORIGIN.
-rpath
Run path, "caminho de execução": uma pasta gravada dentro do executável, onde o carregador vai procurar as .so.
'$ORIGIN'
Quer dizer "a pasta onde está o próprio executável". Assim, a dupla app + libloja.so pode ser copiada junta para qualquer pasta. As aspas simples impedem o Bash de tentar trocar o $ORIGIN por uma variável.
readelf -d
Mostra a parte "dinâmica" do executável. A linha RUNPATH é o -rpath gravado.
Solução 3: instalar no sistema. As bibliotecas que vêm pelo apt ficam em /usr/lib/x86_64-linux-gnu, onde o carregador já procura. As suas costumam ir para /usr/local/lib (copiar a .so para lá e rodar sudo ldconfig, que atualiza o índice do carregador). Isso precisa de permissão de administrador e muda o sistema todo; para estudar, fique com o -rpath.

A vantagem: trocar a biblioteca sem recompilar o programa

A Ana lança a versão 2.0: o frete agora é grátis até 15 km. Ela muda o total.c (a versão) e o frete.c, refaz só a libloja.so e roda o app antigo, sem recompilá-lo:

#include "loja.h"

int loja_frete(int km) {
    if (km <= 15) {
        return 0;
    }
    return 5 + (km - 15);
}
$ ./app
Biblioteca da loja, versão 1.0
3 cadernos: R$ 36
Frete (8 km): R$ 0
Frete (23 km): R$ 18
$ gcc -std=c23 -Wall -Wextra -Iinclude -fPIC -c src/total.c src/frete.c
$ gcc -shared total.o frete.o -o libloja.so
$ ./app
Biblioteca da loja, versão 2.0
3 cadernos: R$ 36
Frete (8 km): R$ 0
Frete (23 km): R$ 13

O mesmo app agora diz 2.0. É assim que uma atualização do sistema corrige um erro na libc de todos os programas de uma vez, sem recompilar nenhum. O cuidado: a .so nova tem de continuar oferecendo as mesmas funções, com os mesmos parâmetros. Se a Ana mudasse o loja_frete para receber um double, os programas antigos passariam um int e receberiam lixo, sem aviso nenhum.

Com .a e .so na mesma pasta

Quando as duas existem, o -lloja escolhe a compartilhada. Para usar a estática, passe o arquivo pelo nome:

$ ar rcs libloja.a total.o frete.o
$ gcc -std=c23 -Wall -Wextra -Iinclude main.c -L. -lloja -o app_qual
$ ldd ./app_qual | grep loja
	libloja.so => not found
$ gcc -std=c23 -Wall -Wextra -Iinclude main.c libloja.a -o app_estatico
$ ldd ./app_estatico | grep loja
$ ./app_estatico | head -1
Biblioteca da loja, versão 2.0

O app_qual precisa da libloja.so (e, sem -rpath, não a acha); o ldd do app_estatico não mostra nenhuma linha com "loja". Com uma biblioteca tão pequena, os tamanhos quase não mudam:

$ ls -l app app_estatico
-rwxrwxr-x 1 ana ana 16056 out 17 11:30 app
-rwxrwxr-x 1 ana ana 16120 out 17 11:30 app_estatico

Com bibliotecas grandes, a diferença aparece. A opção -static pede a versão estática de todas as bibliotecas, até da libc e da libm. O programa da raiz quadrada da seção 1:

$ gcc -std=c23 -Wall -Wextra raiz.c -o raiz -lm
$ gcc -std=c23 -Wall -Wextra -static raiz.c -o raiz_estatico -lm
$ ls -l raiz raiz_estatico
-rwxrwxr-x 1 ana ana  15992 out 17 11:34 raiz
-rwxrwxr-x 1 ana ana 817064 out 17 11:34 raiz_estatico
$ ldd ./raiz_estatico
	não é um executável dinâmico
$ ./raiz_estatico
Lado do quadrado: 7.0 m

De 16 KB para 800 KB, por causa das fotocópias da libc e da libm; em troca, o raiz_estatico roda em qualquer Linux de 64 bits, sem depender das bibliotecas instaladas. "Não é um executável dinâmico" (not a dynamic executable) é o ldd dizendo que não há nenhuma .so para carregar.

Sem -fPIC

Com código simples, como o da lojinha, esquecer o -fPIC às vezes passa. Mas basta uma variável global pública na biblioteca para o ligador recusar. Na pasta ~/c/fpic:

int loja_vendas = 0;

void loja_vender(int valor) {
    loja_vendas += valor;
}
$ gcc -std=c23 -Wall -Wextra -c vendas.c
$ gcc -shared vendas.o -o libvendas.so
/usr/bin/x86_64-linux-gnu-ld.bfd: vendas.o: aviso: relocalização contra "loja_vendas" em secção só de leitura ".text"
/usr/bin/x86_64-linux-gnu-ld.bfd: vendas.o: relocalização R_X86_64_PC32 contra símbolo "loja_vendas" não pode ser usada ao fazer um objecto partilhado; recompilar com -fPIC
/usr/bin/x86_64-linux-gnu-ld.bfd: vinculação final falhou: mau valor
collect2: error: ld returned 1 exit status
$ gcc -std=c23 -Wall -Wextra -fPIC -c vendas.c
$ gcc -shared vendas.o -o libvendas.so && echo ok
ok

A frase que importa está no fim da segunda linha: "recompilar com -fPIC" (em inglês: relocation R_X86_64_PC32 against symbol `loja_vendas' can not be used when making a shared object; recompile with -fPIC). A tradução do ligador mistura português de Portugal ("objecto partilhado", "secção"), mas o recado é claro. Regra: todo .o que vai para uma .so é compilado com -fPIC.


Qual usar

SituaçãoUsePor quê
Um programa que você vai copiar para outro computador, sem instalar nadaEstática (.a)Tudo vai dentro do executável
Código seu, dividido em partes, usado por um ou dois programasEstática (.a)Mais simples: nada de -fPIC, -rpath ou "not found"
Uma biblioteca usada por muitos programas, que recebe correçõesCompartilhada (.so)Uma cópia só no disco e na memória; corrigir a .so corrige todos
Bibliotecas do sistema (libc, libm, as do apt)Compartilhada, que é o padrãoÉ o que o gcc escolhe quando as duas existem

Para começar: faça a sua primeira biblioteca estática. Quando ela funcionar, a compartilhada é só trocar o ar rcs por -fPIC + -shared e cuidar do -rpath. Em projetos maiores, o CMake faz as duas com add_library(loja STATIC …) ou add_library(loja SHARED …).


Quando dá errado

fatal error: loja.h: Arquivo ou diretório inexistente

O que significa: o compilador não achou o cabeçalho da biblioteca.

Como consertar: diga a pasta com -I: -Iinclude, -I../loja/include. Para bibliotecas do sistema, quase sempre falta instalar o pacote de desenvolvimento (no Debian e no Ubuntu, os que terminam em -dev).

não foi possível localizar -lloja: Arquivo ou diretório inexistente (cannot find -lloja)

O que significa: o ligador não achou libloja.so nem libloja.a nas pastas da lista.

Como consertar: acrescente a pasta com -L. Confira também o nome: o -l leva o nome sem lib e sem extensão. Com -llibloja, o ligador até dá a dica:

$ gcc -std=c23 -Wall -Wextra -Iinclude main.c -L. -llibloja -o x
/usr/bin/x86_64-linux-gnu-ld.bfd: não foi possível localizar -llibloja: Arquivo ou diretório inexistente
/usr/bin/x86_64-linux-gnu-ld.bfd: para vincular com ./libloja.a use -l:libloja.a ou renome-o para a liblibloja.a
collect2: error: ld returned 1 exit status
$ gcc -std=c23 -Wall -Wextra -Iinclude main.c -L. -lloja.so -o x
/usr/bin/x86_64-linux-gnu-ld.bfd: não foi possível localizar -lloja.so: Arquivo ou diretório inexistente
collect2: error: ld returned 1 exit status
referência não definida para "loja_total", com a biblioteca no comando

O que significa: quase sempre, a biblioteca veio antes dos arquivos que a usam (gcc -L. -lloja main.o). Pode ser também uma função que está no .h, mas não em nenhum .o da biblioteca (confira com nm libloja.a).

Como consertar: os seus .c e .o primeiro, as bibliotecas no fim: gcc main.o -L. -lloja -o app.

error while loading shared libraries: libloja.so: cannot open shared object file

O que significa: o programa ligou, mas o carregador não acha a .so na hora de rodar. O código de saída é 127. O ldd ./app mostra libloja.so => not found.

Como consertar: para testar, LD_LIBRARY_PATH=. ./app. Para resolver, ligue com -Wl,-rpath,'$ORIGIN' (e deixe a .so ao lado do programa) ou instale a biblioteca no sistema.

relocalização R_X86_64_PC32 … não pode ser usada ao fazer um objecto partilhado; recompilar com -fPIC

O que significa: um .o foi compilado sem -fPIC e está indo para uma .so.

Como consertar: apague os .o e compile de novo com -fPIC: gcc -fPIC -c ….

Com a .so, liga sem erro, mas a biblioteca calcula errado

O que significa: é o caso da arredondar da seção 3, agora com a biblioteca compartilhada. Com a .a, o ligador reclamou de múltiplas definições. Com a .so, ele não reclama: o nome do programa "ganha", e a própria biblioteca passa a chamar a arredondar das notas.

$ gcc -std=c23 -Wall -Wextra -Iinclude main.c -L. -lloja -Wl,-rpath,'$ORIGIN' -o app_so
$ ./app_so
Frete (23 km): R$ 15
Nota 7 vira 10

O frete deveria ser R$ 20 (5 + 15), e saiu R$ 15 (5 + 10, a "nota arredondada" de 13).

Como consertar: static em toda função interna da biblioteca. Com a .so refeita, sem recompilar o programa:

$ sed -i 's/^int arredondar/static int arredondar/' src/frete.c
$ gcc -std=c23 -Wall -Wextra -Iinclude -fPIC -c src/frete.c
$ gcc -shared total.o frete.o -o libloja.so
$ ./app_so
Frete (23 km): R$ 20
Nota 7 vira 10

(O sed -i troca o texto dentro do arquivo: aqui, põe o static antes da int arredondar.)


Referência

ComandoO que faz
gcc -Iinclude -c src/a.cCompila, procurando os .h também em include/
ar rcs libnome.a a.o b.oCria (ou atualiza) a biblioteca estática
ar t libnome.aLista os .o de dentro dela
gcc -fPIC -c a.cCompila um .o que pode ir para uma .so
gcc -shared a.o b.o -o libnome.soCria a biblioteca compartilhada
gcc main.o -L. -lnome -o appLiga com a libnome da pasta atual (prefere a .so); bibliotecas depois dos seus arquivos
gcc main.o libnome.a -o appLiga com a estática, pelo nome do arquivo
-Wl,-rpath,'$ORIGIN'Grava no programa: "procure as .so na minha pasta"
LD_LIBRARY_PATH=pasta ./appRoda o programa procurando as .so também na pasta
ldd ./appLista as .so de que o programa precisa e onde estão (ou not found)
nm libnome.a / nm -D libnome.soOs símbolos da biblioteca
file arquivocurrent ar archive (.a), shared object (.so)

Exercícios

1. A opção da biblioteca

O arquivo se chama libestoque.so. Complete a opção que o liga:

gcc main.o -L. -o app

2. A caixa de .o

Complete o comando que cria a biblioteca estática libestoque.a:

libestoque.a produtos.o precos.o

3. Para uma .so

Que opção de compilação todo .o de uma biblioteca compartilhada deve receber?

gcc -c produtos.c

4. O comando gcc -L. -lloja main.o -o app dá referência não definida para "loja_total", mas a libloja.a tem essa função. Por quê?

5. O programa foi ligado com uma libloja.so, e a Ana apagou a .so. O que acontece no ./app?

Missão final: lance a versão 2.0

Missão: a Ana vai lançar a versão 2.0 da biblioteca da lojinha. Mexa só nas abas da biblioteca (total.c e frete.c); o main.c é o programa de um cliente e não muda, como com uma .so trocada:

  1. a loja_versao passa a devolver "2.0";
  2. o frete fica grátis até 15 km; acima disso, R$ 5 mais R$ 1 por quilômetro além dos 15.
Biblioteca da loja, versão 2.0
3 cadernos: R$ 36
Frete (8 km): R$ 0
Frete (23 km): R$ 13
Frete (40 km): R$ 30
#include <stdio.h>
#include "loja.h"

int main(void) {
    printf("Biblioteca da loja, versão %s\n", loja_versao());
    printf("3 cadernos: R$ %d\n", loja_total(12, 3));
    printf("Frete (8 km): R$ %d\n", loja_frete(8));
    printf("Frete (23 km): R$ %d\n", loja_frete(23));
    printf("Frete (40 km): R$ %d\n", loja_frete(40));
    return 0;
}
#include "loja.h"

const char *loja_versao(void) {
    return "1.0";
}

int loja_total(int preco, int quantidade) {
    return preco * quantidade;
}
#include "loja.h"

int loja_frete(int km) {
    if (km <= 10) {
        return 0;
    }
    return 5 + (km - 10);
}
#ifndef LOJA_H
#define LOJA_H

const char *loja_versao(void);
int loja_total(int preco, int quantidade);
int loja_frete(int km);

#endif
#include "loja.h"

const char *loja_versao(void) {
    return "2.0";
}

int loja_total(int preco, int quantidade) {
    return preco * quantidade;
}
#include "loja.h"

int loja_frete(int km) {
    if (km <= 15) {
        return 0;
    }
    return 5 + (km - 15);
}

Resumo

  • Uma biblioteca é um pacote de funções compiladas. O nome segue o padrão libnome.a ou libnome.so, e o -lnome a liga. Você já usa a libc (sempre) e a libm (-lm).
  • Estática: ar rcs libloja.a *.o. O ligador copia as funções para o programa, que depois roda sem ela.
  • Compartilhada: gcc -fPIC -c e gcc -shared … -o libloja.so. O carregador a abre a cada execução; trocar a .so atualiza todos os programas, sem recompilar.
  • -I diz onde estão os .h; -L, onde estão as bibliotecas (na ligação); -l, qual biblioteca. As bibliotecas vão depois dos seus arquivos no comando.
  • Na hora de rodar, o -L não vale: use -Wl,-rpath,'$ORIGIN' ou LD_LIBRARY_PATH. O ldd mostra o que o programa precisa e o que está not found.
  • Funções internas da biblioteca levam static, e as públicas, um prefixo (loja_). Com uma .so, um nome repetido não dá erro: dá resultado errado.