updates
This commit is contained in:
@@ -0,0 +1,20 @@
|
|||||||
|
CC = gcc
|
||||||
|
CFLAGS = -std=c17 -Wall -Wextra -Wpedantic -g
|
||||||
|
CPPFLAGS = -Iinclude
|
||||||
|
|
||||||
|
all: build/hello
|
||||||
|
|
||||||
|
build/escpos.o: src/escpos.c include/escpos.h
|
||||||
|
$(CC) $(CFLAGS) $(CPPFLAGS) -c src/escpos.c -o build/escpos.o
|
||||||
|
|
||||||
|
build/hello.o: examples/hello.c include/escpos.h
|
||||||
|
$(CC) $(CFLAGS) $(CPPFLAGS) -c examples/hello.c -o build/hello.o
|
||||||
|
|
||||||
|
build/hello: build/hello.o build/escpos.o
|
||||||
|
$(CC) build/hello.o build/escpos.o -o build/hello
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf build/*
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: all clean
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
# Progreso
|
||||||
|
|
||||||
|
## Estado actual
|
||||||
|
|
||||||
|
- **Fase 1** — Estructura del proyecto.
|
||||||
|
- Paso actual: Makefile. Hecho: reglas con dependencias correctas, variables CC/CFLAGS/CPPFLAGS, clean, .PHONY, cero warnings con gcc y clang. Siguiente: .gitignore y carpeta build/ en un clon limpio; después reglas patrón.
|
||||||
|
- Entorno activo: Fedora 42 nativo (gcc 15.2, clang 20, make 4.4, gdb 17,
|
||||||
|
valgrind 3.26).
|
||||||
|
|
||||||
|
## Hecho
|
||||||
|
|
||||||
|
- `include/escpos.h` + `src/escpos.c` con `escpos_version()`, y `hello.c` que
|
||||||
|
la usa. Compila y enlaza a mano en Windows y en Fedora.
|
||||||
|
- `samples/hello_world.bin`: ESC @, texto, LF, ESC d 5, GS V 0.
|
||||||
|
- Emulador ESC/POS en Go (`emulator/`) para probar sin gastar papel.
|
||||||
|
|
||||||
|
## Conceptos de C aprendidos
|
||||||
|
|
||||||
|
- Etapas de compilación, unidades de traducción, cabeceras, *include guards*,
|
||||||
|
`static` para encapsular, `nm` y errores del enlazador.
|
||||||
|
- Cadenas terminadas en `'\0'`, `const char *`.
|
||||||
|
- Memoria virtual de un proceso, modo usuario y modo núcleo.
|
||||||
|
|
||||||
|
## Decisiones de diseño
|
||||||
|
|
||||||
|
- Prefijo `escpos_` para toda la API pública.
|
||||||
|
|
||||||
|
## Pendiente / puntos débiles
|
||||||
|
|
||||||
|
- Hay binarios (`.o`, `.exe`) versionados en git. Falta `.gitignore`.
|
||||||
|
- Ficheros sin salto de línea final: corregido; editor configurado.
|
||||||
|
- Impresoras físicas: sin registrar marca y modelo.
|
||||||
@@ -20,6 +20,54 @@
|
|||||||
Sin flags de parada, gcc hace la cadena completa. Si no le das `-o`, el
|
Sin flags de parada, gcc hace la cadena completa. Si no le das `-o`, el
|
||||||
ejecutable se llama `a.exe` en Windows y `a.out` en Linux.
|
ejecutable se llama `a.exe` en Windows y `a.out` en Linux.
|
||||||
|
|
||||||
|
Si juntas dos flags de parada, gana la que para antes: `gcc -E -S` se comporta
|
||||||
|
como `-E`. Además, `-o` no convierte nada: `gcc -E x.c -o x.o` guarda texto C
|
||||||
|
preprocesado en un fichero que se llama `.o`, aunque no sea un fichero objeto.
|
||||||
|
|
||||||
|
### Empezar por cualquier etapa
|
||||||
|
|
||||||
|
gcc decide por la **extensión** del fichero de entrada en qué etapa empieza:
|
||||||
|
|
||||||
|
| Entrada | Empieza en |
|
||||||
|
|---|---|
|
||||||
|
| `.c` | preprocesador |
|
||||||
|
| `.i` | compilador (ya está preprocesado; sobra `-I`) |
|
||||||
|
| `.s` | ensamblador |
|
||||||
|
| `.o` | enlazador |
|
||||||
|
|
||||||
|
Por ejemplo, `gcc -c build/escpos.i -o build/escpos.o` compila un fichero ya
|
||||||
|
preprocesado.
|
||||||
|
|
||||||
|
Lo normal es partir del `.c`: gcc hace la cadena completa y crea el `.i` y el
|
||||||
|
`.s` como ficheros temporales que borra al acabar. Parar en cada etapa solo
|
||||||
|
sirve para aprender o para depurar. `-save-temps` hace la cadena completa y
|
||||||
|
**conserva** los intermedios en la carpeta actual.
|
||||||
|
|
||||||
|
**En Go**: `go build` también genera intermedios, pero en una carpeta temporal
|
||||||
|
que no ves (`go build -work` imprime cuál es y no la borra).
|
||||||
|
|
||||||
|
### Qué hay dentro de un `.i`
|
||||||
|
|
||||||
|
Es el `.c` después del preprocesador: C puro, sin `#include`, `#define` ni
|
||||||
|
`#ifndef`. Es exactamente lo que lee el compilador.
|
||||||
|
|
||||||
|
- Cada `#include` ha desaparecido y en su lugar está **el texto del `.h`
|
||||||
|
pegado**. Esto va en cadena: `stdio.h` incluye otras cabeceras, que a su vez
|
||||||
|
incluyen otras. Un `hello.c` de 10 líneas se convierte en un `.i` de unas 800.
|
||||||
|
- Los *include guards* (`#ifndef`/`#define`/`#endif`) ya se han ejecutado y no
|
||||||
|
aparecen. Solo queda lo que había dentro.
|
||||||
|
- Las líneas como `# 1 "include/escpos.h" 1` no son código. Son **marcas de
|
||||||
|
línea**: le indican al compilador de qué fichero y de qué línea viene cada
|
||||||
|
trozo, para que un error diga `escpos.h:4` en vez de "línea 11 del `.i`".
|
||||||
|
- **En el `.i` no hay código de otros `.c`.** De `escpos.h` solo llega la
|
||||||
|
declaración `const char *escpos_version(void);`, sin cuerpo. Con `printf`
|
||||||
|
pasa igual: el `.i` trae `extern int printf(...);` y su código está en la
|
||||||
|
librería de C del sistema (`libc.so` en Linux), ya compilado.
|
||||||
|
|
||||||
|
**En Go**: no hay preprocesador. `import` no pega texto, y además de darte las
|
||||||
|
firmas mete el código del paquete en el binario. En C son dos cosas separadas:
|
||||||
|
las firmas llegan con `#include` al preprocesar, y el código llega al enlazar.
|
||||||
|
|
||||||
**En Go**: `go build` hace lo mismo por dentro (`go tool compile` genera un
|
**En Go**: `go build` hace lo mismo por dentro (`go tool compile` genera un
|
||||||
`.o` por paquete y `go tool link` los enlaza), pero no lo ves.
|
`.o` por paquete y `go tool link` los enlaza), pero no lo ves.
|
||||||
|
|
||||||
@@ -89,6 +137,15 @@ no sabe nada de `escpos.c`. Cada `.c`, junto con todo lo que incluye, forma una
|
|||||||
función existe y con qué firma. Puede repetirse las veces que quieras.
|
función existe y con qué firma. Puede repetirse las veces que quieras.
|
||||||
- **Definición**: la función con su cuerpo `{ ... }`. Debe aparecer **una sola
|
- **Definición**: la función con su cuerpo `{ ... }`. Debe aparecer **una sola
|
||||||
vez** en todo el programa. Es la *regla de una definición*.
|
vez** en todo el programa. Es la *regla de una definición*.
|
||||||
|
|
||||||
|
**Para compilar una llamada basta con la declaración.** Al compilar
|
||||||
|
`hello.c`, gcc no tiene el código de `escpos_version`, y no le hace falta.
|
||||||
|
Con saber que existe, qué recibe y qué devuelve, comprueba que la llamada es
|
||||||
|
correcta y genera la instrucción de llamada **con la dirección en blanco**.
|
||||||
|
Ese hueco lo rellena después el enlazador. Por eso
|
||||||
|
`gcc -c examples/hello.c` funciona sin `escpos.c`, y `nm build/hello.o`
|
||||||
|
muestra `U escpos_version`. Es como programar en Go contra una interfaz: el
|
||||||
|
compilador comprueba las firmas sin conocer la implementación.
|
||||||
- **`static`** delante de una función la hace privada a su `.c`. Es lo más
|
- **`static`** delante de una función la hace privada a su `.c`. Es lo más
|
||||||
parecido a la minúscula inicial de Go.
|
parecido a la minúscula inicial de Go.
|
||||||
|
|
||||||
@@ -150,6 +207,16 @@ hacen dos programas distintos:
|
|||||||
- En las cabeceras van declaraciones, tipos y macros. **Nunca cuerpos de
|
- En las cabeceras van declaraciones, tipos y macros. **Nunca cuerpos de
|
||||||
funciones**: cada `.c` que incluyera la cabecera tendría su propia definición
|
funciones**: cada `.c` que incluyera la cabecera tendría su propia definición
|
||||||
y el enlazador daría `multiple definition`.
|
y el enlazador daría `multiple definition`.
|
||||||
|
Comprobado: con el cuerpo en `escpos.h`, `nm` muestra `T escpos_version`
|
||||||
|
en `hello.o` **y** en `escpos.o`, y el enlazado falla.
|
||||||
|
- **Si cambias un `.h`, hay que recompilar todos los `.c` que lo incluyen.**
|
||||||
|
Si no lo haces, enlazas `.o` compilados con la versión vieja de la cabecera,
|
||||||
|
y ni el compilador ni el enlazador avisan. Nos pasó: después de mover el
|
||||||
|
cuerpo a `escpos.h` solo se recompiló `escpos.o`. El `hello.o` viejo seguía
|
||||||
|
teniendo `U escpos_version`, así que todo enlazaba y parecía que no pasaba
|
||||||
|
nada. Hay que comparar las fechas (`ls -l`) o, mejor, dejar que un Makefile
|
||||||
|
lleve la cuenta. **En Go** esto no puede pasar: `go build` sabe qué depende
|
||||||
|
de qué y recompila lo necesario.
|
||||||
- Las funciones `static` van en el `.c`. Metidas en un `.h`, cada `.o` recibe
|
- Las funciones `static` van en el `.c`. Metidas en un `.h`, cada `.o` recibe
|
||||||
una copia privada: el código se duplica y aparecen avisos de "no usada".
|
una copia privada: el código se duplica y aparecen avisos de "no usada".
|
||||||
|
|
||||||
@@ -218,6 +285,12 @@ En C17, `int f();` significa "parámetros **sin especificar**", así que
|
|||||||
| `-E` / `-S` / `-c` | control | Paran tras preprocesar, compilar o ensamblar. |
|
| `-E` / `-S` / `-c` | control | Paran tras preprocesar, compilar o ensamblar. |
|
||||||
| `-o nombre` | control | Nombre del fichero de salida. |
|
| `-o nombre` | control | Nombre del fichero de salida. |
|
||||||
|
|
||||||
|
Los flags que llevan un valor admiten el valor pegado o separado: `-Iinclude`
|
||||||
|
y `-I include` son lo mismo, igual que `-obuild/x.o` y `-o build/x.o`. Por
|
||||||
|
costumbre, `-I`, `-L`, `-l` y `-D` se escriben pegados (`-lws2_32`,
|
||||||
|
`-DDEBUG`), y `-o` separado. Los flags largos como `-std=c17` llevan siempre
|
||||||
|
`=`. **En Go** pasa algo parecido con el paquete `flag`: vale `-o x` y `-o=x`.
|
||||||
|
|
||||||
Todo menos `-o` se usa **al compilar**. Al enlazar solo se pasan los `.o`,
|
Todo menos `-o` se usa **al compilar**. Al enlazar solo se pasan los `.o`,
|
||||||
`-o` y, más adelante, las librerías (por ejemplo, `-lws2_32` para Winsock).
|
`-o` y, más adelante, las librerías (por ejemplo, `-lws2_32` para Winsock).
|
||||||
|
|
||||||
@@ -257,12 +330,29 @@ Pueden aparecer símbolos que no escribiste tú. Por ejemplo, `puts`: gcc
|
|||||||
cambia `printf("%s\n", s)` por `puts(s)`. Y `__main`, que es el código de
|
cambia `printf("%s\n", s)` por `puts(s)`. Y `__main`, que es el código de
|
||||||
arranque de MinGW.
|
arranque de MinGW.
|
||||||
|
|
||||||
|
Ese `U puts` no da `undefined reference` aunque no le pases nada al enlazador
|
||||||
|
para resolverlo, porque gcc añade siempre la librería de C (`libc`) al enlazar.
|
||||||
|
Las demás librerías hay que pedirlas explícitamente con `-l`.
|
||||||
|
|
||||||
## Errores: compilador frente a enlazador
|
## Errores: compilador frente a enlazador
|
||||||
|
|
||||||
- **Compilador**: el mensaje empieza por `fichero.c:línea:columna:`, porque
|
- **Compilador**: el mensaje empieza por `fichero.c:línea:columna:`, porque
|
||||||
está leyendo código fuente.
|
está leyendo código fuente.
|
||||||
- **Enlazador**: el mensaje menciona `ld.exe` o `collect2`, además de ficheros
|
- **Enlazador**: el mensaje menciona `ld.exe` (Windows), `/usr/bin/ld` (Linux)
|
||||||
`.o` y nombres de símbolo. A esa etapa ya no llega el código fuente.
|
o `collect2`, además de ficheros `.o` y nombres de símbolo. A esa etapa ya no
|
||||||
|
llega el código fuente. `collect2` es el programa de gcc que lanza `ld`.
|
||||||
|
|
||||||
|
Ejemplo real, enlazando `hello.o` sin `escpos.o`:
|
||||||
|
|
||||||
|
```
|
||||||
|
/usr/bin/ld: build/hello.o: in function `main':
|
||||||
|
.../examples/hello.c:6:(.text+0x9): undefined reference to `escpos_version'
|
||||||
|
collect2: error: ld returned 1 exit status
|
||||||
|
```
|
||||||
|
|
||||||
|
Sale `hello.c:6` aunque el enlazador no lee código fuente. Ese dato viene de la
|
||||||
|
información de depuración que `-g` guardó en el `.o`. Sin `-g` solo verías
|
||||||
|
`(.text+0x9)`, que es la posición dentro del código máquina.
|
||||||
|
|
||||||
## Warning frente a error
|
## Warning frente a error
|
||||||
|
|
||||||
@@ -277,6 +367,25 @@ ejecutando un binario viejo creyendo que es el nuevo. Para evitarlo:
|
|||||||
valor es fallo.
|
valor es fallo.
|
||||||
- `make` se detiene en cuanto un comando falla.
|
- `make` se detiene en cuanto un comando falla.
|
||||||
|
|
||||||
|
## Salto de línea al final del fichero
|
||||||
|
|
||||||
|
C17 exige que todo fichero fuente no vacío termine en `\n`. Si no, el
|
||||||
|
comportamiento es indefinido. El riesgo está en `#include`, que pega el
|
||||||
|
texto: la última línea del `.h` (`#endif`) podría quedar unida a la siguiente
|
||||||
|
línea del fichero que lo incluye. Los preprocesadores actuales lo arreglan por
|
||||||
|
dentro, pero sigue sin ser C correcto.
|
||||||
|
|
||||||
|
- Clang avisa con `-Wpedantic`: `warning: no newline at end of file
|
||||||
|
[-Wnewline-eof]`. gcc ya no avisa.
|
||||||
|
- Para verlo: `tail -c 5 fichero.c | xxd`. El último byte tiene que ser `0a`.
|
||||||
|
- Solución: configurar el editor (`"files.insertFinalNewline": true` en
|
||||||
|
VS Code).
|
||||||
|
|
||||||
|
Compilar con gcc **y** con clang da dos juegos de avisos distintos, sin coste:
|
||||||
|
`make clean && make CC=clang`.
|
||||||
|
|
||||||
|
**En Go**: `gofmt` añade el salto final automáticamente.
|
||||||
|
|
||||||
## Varios
|
## Varios
|
||||||
|
|
||||||
- En bash, un programa de la carpeta actual se ejecuta con `./hello.exe`. Bash
|
- En bash, un programa de la carpeta actual se ejecuta con `./hello.exe`. Bash
|
||||||
|
|||||||
+136
@@ -0,0 +1,136 @@
|
|||||||
|
# Make
|
||||||
|
|
||||||
|
## Qué hacía Go por mí
|
||||||
|
|
||||||
|
`go build` hace tres cosas sin que las pidas:
|
||||||
|
|
||||||
|
1. Lee los `import` y deduce qué paquetes dependen de cuáles.
|
||||||
|
2. Recompila solo lo que cambió, usando la caché de `$GOCACHE`, que va por
|
||||||
|
hash del contenido.
|
||||||
|
3. Llama al compilador y al enlazador con los flags correctos para cada
|
||||||
|
plataforma (`GOOS`/`GOARCH`).
|
||||||
|
|
||||||
|
En C no pasa nada de eso. `gcc` compila lo que le pases y nada más. `make` es
|
||||||
|
la herramienta clásica para escribir a mano ese grafo de dependencias.
|
||||||
|
|
||||||
|
## El modelo: un grafo de ficheros con fechas
|
||||||
|
|
||||||
|
Un Makefile es una lista de **reglas**:
|
||||||
|
|
||||||
|
```make
|
||||||
|
objetivo: prerrequisitos
|
||||||
|
receta # OJO: la línea empieza por TABULADOR, no por espacios
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Objetivo**: el fichero que se quiere producir, por ejemplo `build/escpos.o`.
|
||||||
|
- **Prerrequisitos**: los ficheros de los que depende.
|
||||||
|
- **Receta**: los comandos de shell que producen el objetivo.
|
||||||
|
|
||||||
|
Make rehace un objetivo si no existe o si **algún prerrequisito tiene una fecha
|
||||||
|
de modificación más reciente** que él. No usa hashes como Go: solo compara
|
||||||
|
fechas (`mtime`). Por eso un `touch` basta para forzar una recompilación.
|
||||||
|
|
||||||
|
**Make no entiende la receta.** Para Make, la receta es texto que pasa a la
|
||||||
|
shell sin leerlo. Que ponga `-Iinclude` no le dice nada: `-I` es un flag de gcc,
|
||||||
|
no de Make. Make solo mira la línea `objetivo: prerrequisitos`. Si un fichero
|
||||||
|
no aparece ahí, sus cambios no provocan ninguna recompilación.
|
||||||
|
|
||||||
|
**El objetivo debe llamarse como el fichero que produce la receta.** Si la
|
||||||
|
regla se llama `link/all` pero genera `build/hello`, Make busca un fichero
|
||||||
|
`link/all`. Como nunca existe, ejecuta la receta siempre.
|
||||||
|
|
||||||
|
**Se declaran dependencias, no pasos.** No hace falta que una receta llame a
|
||||||
|
`make` para construir otras cosas (`make[1]: Entering directory...` indica que
|
||||||
|
se está lanzando otro Make). Basta con escribir de qué depende cada objetivo, y
|
||||||
|
Make deduce el orden:
|
||||||
|
|
||||||
|
```make
|
||||||
|
all: build/hello # sin receta: solo "depende de"
|
||||||
|
build/hello: build/hello.o build/escpos.o # enlazar
|
||||||
|
build/hello.o: examples/hello.c include/escpos.h
|
||||||
|
build/escpos.o: src/escpos.c include/escpos.h
|
||||||
|
```
|
||||||
|
|
||||||
|
**En Go** es la diferencia entre llamar a `go build` paquete por paquete desde
|
||||||
|
un script y dejar que `go build` resuelva los imports.
|
||||||
|
|
||||||
|
Make recorre el grafo desde el objetivo que le pides (o el primero del fichero)
|
||||||
|
hacia sus prerrequisitos, en profundidad.
|
||||||
|
|
||||||
|
## Piezas que vas a necesitar
|
||||||
|
|
||||||
|
| Pieza | Qué es |
|
||||||
|
|---|---|
|
||||||
|
| `CC = gcc` | Variable. Se usa como `$(CC)`. |
|
||||||
|
| `CFLAGS`, `CPPFLAGS`, `LDFLAGS` | Nombres convencionales: flags de compilación, del preprocesador (`-Iinclude`) y del enlazado. |
|
||||||
|
| `$@` | Nombre del objetivo de la regla. |
|
||||||
|
| `$<` | Primer prerrequisito. |
|
||||||
|
| `$^` | Todos los prerrequisitos. |
|
||||||
|
| `build/%.o: src/%.c` | Regla patrón: sirve para cualquier `.c` → `.o`. |
|
||||||
|
| `.PHONY: all clean` | Objetivos que no son ficheros. Sin esto, si existiera un fichero llamado `clean`, `make clean` no haría nada. |
|
||||||
|
| `$(wildcard src/*.c)` | Lista de ficheros que existen. |
|
||||||
|
| `$(patsubst src/%.c,build/%.o,$(SRCS))` | Transforma una lista de nombres. |
|
||||||
|
|
||||||
|
### `.PHONY`: objetivos que no son ficheros
|
||||||
|
|
||||||
|
`clean` y `all` son nombres de acciones, no ficheros. Pero Make no lo sabe: si
|
||||||
|
alguien crea un fichero llamado `clean`, la regla (sin prerrequisitos) queda
|
||||||
|
"al día" y `make clean` responde `'clean' is up to date` sin borrar nada.
|
||||||
|
Comprobado con `touch clean`.
|
||||||
|
|
||||||
|
```make
|
||||||
|
.PHONY: all clean
|
||||||
|
```
|
||||||
|
|
||||||
|
Con esto Make no busca ningún fichero con esos nombres y ejecuta la receta
|
||||||
|
siempre.
|
||||||
|
|
||||||
|
**En proyectos Go** Make se usa como lanzador de tareas (`migrate`, `test`,
|
||||||
|
`lint`, `run`...). Ninguna produce un fichero, y hasta `build` se delega en
|
||||||
|
`go build`, que tiene su propia caché y conoce las dependencias mejor que
|
||||||
|
Make. Por eso allí todo va en `.PHONY` y la comparación de fechas no se usa
|
||||||
|
nunca. En C es al revés: Make **es** el sistema de compilación, y solo las
|
||||||
|
acciones (`all`, `clean`) son phony.
|
||||||
|
|
||||||
|
### Variables desde la línea de comandos
|
||||||
|
|
||||||
|
`make CC=clang` sobrescribe la variable `CC` del Makefile solo para esa
|
||||||
|
ejecución. Pero **Make no se da cuenta de que han cambiado el compilador o los
|
||||||
|
flags**, porque solo compara fechas de ficheros. Si los `.o` están al día, no
|
||||||
|
hace nada, y si recompila alguno, mezcla `.o` de gcc con `.o` de clang. Cuando
|
||||||
|
cambies compilador o flags: `make clean` y construir de cero.
|
||||||
|
|
||||||
|
**En Go** los flags y la versión del compilador forman parte del hash de la
|
||||||
|
caché, así que `go build` sí recompila lo necesario.
|
||||||
|
|
||||||
|
## El problema de las cabeceras
|
||||||
|
|
||||||
|
Si `escpos.c` incluye `escpos.h` y cambias solo el `.h`, make no lo sabe:
|
||||||
|
la regla `build/%.o: src/%.c` no menciona el `.h`. Resultado: un `.o` compilado
|
||||||
|
con la versión vieja de la cabecera. Es un error silencioso que en Go no
|
||||||
|
existe.
|
||||||
|
|
||||||
|
La solución es que gcc genere las dependencias por ti:
|
||||||
|
|
||||||
|
- `-MMD` hace que, al compilar `x.c`, gcc escriba también `x.d`, un fragmento
|
||||||
|
de Makefile con las cabeceras que incluyó.
|
||||||
|
- `-MP` añade reglas vacías para cada cabecera, para que borrar un `.h` no
|
||||||
|
rompa el build.
|
||||||
|
- `-include $(DEPS)` carga esos `.d` en el Makefile. El `-` evita el error si
|
||||||
|
todavía no existen.
|
||||||
|
|
||||||
|
## Windows y Linux en el mismo Makefile
|
||||||
|
|
||||||
|
En MSYS2, la variable de entorno `OS` vale `Windows_NT`. En Linux no está
|
||||||
|
definida.
|
||||||
|
|
||||||
|
```make
|
||||||
|
ifeq ($(OS),Windows_NT)
|
||||||
|
EXE = .exe
|
||||||
|
else
|
||||||
|
EXE =
|
||||||
|
endif
|
||||||
|
```
|
||||||
|
|
||||||
|
Más adelante (Fase 4) aquí irán también las librerías que cambian según la
|
||||||
|
plataforma, como `-lws2_32` para Winsock en Windows.
|
||||||
+10
-3
@@ -3,9 +3,10 @@
|
|||||||
Ordenados por tema, en el orden en que se ven en el proyecto.
|
Ordenados por tema, en el orden en que se ven en el proyecto.
|
||||||
|
|
||||||
1. [Compilación y enlazado](01-compilation-and-linking.md): etapas de gcc,
|
1. [Compilación y enlazado](01-compilation-and-linking.md): etapas de gcc,
|
||||||
cómo compila Go, unidades de traducción, cabeceras e `#include`,
|
cómo compila Go, empezar por cualquier etapa (`.i`, `-save-temps`), qué
|
||||||
encapsulación y tipos opacos, *include guards*, flags, qué ve quien recibe
|
hay dentro de un `.i`, unidades de traducción, por qué basta la declaración
|
||||||
los binarios, `nm` y errores del enlazador.
|
para compilar, cabeceras e `#include`, encapsulación y tipos opacos, *include guards*,
|
||||||
|
flags, qué ve quien recibe los binarios, `nm` y errores del enlazador.
|
||||||
2. [Punteros y cadenas](02-pointers-and-strings.md): cadenas terminadas en
|
2. [Punteros y cadenas](02-pointers-and-strings.md): cadenas terminadas en
|
||||||
`'\0'`, `const char *`, los dos significados de `*` y el formato de `printf`.
|
`'\0'`, `const char *`, los dos significados de `*` y el formato de `printf`.
|
||||||
3. [La memoria de un proceso](03-process-memory.md): memoria virtual, fallos
|
3. [La memoria de un proceso](03-process-memory.md): memoria virtual, fallos
|
||||||
@@ -17,3 +18,9 @@ Ordenados por tema, en el orden en que se ven en el proyecto.
|
|||||||
5. [Frameworks y librerías](05-libraries-and-frameworks.md): Qt, por qué en C
|
5. [Frameworks y librerías](05-libraries-and-frameworks.md): Qt, por qué en C
|
||||||
casi no hay frameworks, librerías C relevantes para el proyecto e
|
casi no hay frameworks, librerías C relevantes para el proyecto e
|
||||||
interfaces con punteros a función.
|
interfaces con punteros a función.
|
||||||
|
6. [Make](06-make.md): reglas, grafo de dependencias por fechas, variables
|
||||||
|
automáticas, reglas patrón, dependencias de cabeceras con `-MMD -MP` y
|
||||||
|
detección de Windows/Linux.
|
||||||
|
|
||||||
|
[Chuleta de comandos](cheatsheet.md): todos los comandos usados, agrupados por
|
||||||
|
tarea.
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
# Chuleta de comandos
|
||||||
|
|
||||||
|
Comandos que hemos usado, agrupados por tarea. Los flags del proyecto son
|
||||||
|
`-std=c17 -Wall -Wextra -Wpedantic -g`.
|
||||||
|
|
||||||
|
## Compilar paso a paso
|
||||||
|
|
||||||
|
| Comando | Qué hace |
|
||||||
|
|---|---|
|
||||||
|
| `gcc -E -Iinclude src/escpos.c -o build/escpos.i` | Solo preprocesa. Genera el `.i`: C con los `.h` pegados. |
|
||||||
|
| `gcc -S ...` | Para después de compilar. Genera el `.s` (ensamblador). |
|
||||||
|
| `gcc <flags> -Iinclude -c src/escpos.c -o build/escpos.o` | Compila hasta el `.o` (fichero objeto), sin enlazar. |
|
||||||
|
| `gcc <flags> -c build/escpos.i -o build/escpos.o` | Compila partiendo de un `.i`. No hace falta `-I`. |
|
||||||
|
| `gcc <flags> -Iinclude -save-temps -c src/escpos.c` | Cadena completa, conservando `.i`, `.s` y `.o`. |
|
||||||
|
| `gcc build/hello.o build/escpos.o -o build/hello` | Enlaza los `.o` en un ejecutable. |
|
||||||
|
| `./build/hello` | Ejecuta un programa de la carpeta actual. |
|
||||||
|
|
||||||
|
Si juntas dos flags de parada (`-E -S`), gana la que para antes.
|
||||||
|
|
||||||
|
## Inspeccionar ficheros
|
||||||
|
|
||||||
|
| Comando | Qué hace |
|
||||||
|
|---|---|
|
||||||
|
| `nm build/hello.o` | Lista símbolos: `T` definido y visible, `t` definido y `static`, `U` usado pero definido en otro sitio. |
|
||||||
|
| `wc -l fichero` | Cuenta líneas (por ejemplo, para comparar `.c` y `.i`). |
|
||||||
|
| `ls -l build/` | Muestra la fecha de modificación: sirve para ver qué `.o` está obsoleto. |
|
||||||
|
| `file fichero` | Dice qué es: ELF (Linux), COFF o PE (Windows)... |
|
||||||
|
| `strings fichero.o` | Muestra las cadenas legibles que hay dentro de un binario. |
|
||||||
|
| `objdump -d fichero.o` | Desensambla el código máquina. |
|
||||||
|
| `objdump --dwarf=info fichero.o` | Muestra la información de depuración que añade `-g`. |
|
||||||
|
| `xxd fichero.bin` | Volcado hexadecimal (para revisar los bytes ESC/POS). |
|
||||||
|
| `tail -c 5 fichero \| xxd` | Muestra los últimos bytes: sirve para ver si el fichero acaba en `0a` (`\n`). |
|
||||||
|
| `echo $?` | Código de salida del último comando: `0` es éxito. |
|
||||||
|
|
||||||
|
## Solo en Windows (MSYS2)
|
||||||
|
|
||||||
|
| Comando | Qué hace |
|
||||||
|
|---|---|
|
||||||
|
| `objdump -p hello.exe \| grep "DLL Name"` | Lista las DLL de las que depende el ejecutable. |
|
||||||
|
| `strip hello.exe` | Quita símbolos e información de depuración. |
|
||||||
|
| `gcc -static ...` | Enlaza estáticamente todas las librerías posibles. |
|
||||||
|
|
||||||
|
## Make
|
||||||
|
|
||||||
|
| Comando | Qué hace |
|
||||||
|
|---|---|
|
||||||
|
| `make` | Construye el primer objetivo del `Makefile`. |
|
||||||
|
| `make build/escpos.o` | Construye un objetivo concreto. |
|
||||||
|
| `make CC=clang` | Sobrescribe una variable del Makefile para esta ejecución. No recompila lo que ya está al día. |
|
||||||
|
| `make clean` | Borra lo generado (si el Makefile tiene esa regla y está en `.PHONY`). |
|
||||||
|
| `touch fichero` | Actualiza la fecha del fichero sin cambiar su contenido. Make lo verá como modificado. |
|
||||||
|
| `cat -A Makefile` | Muestra los caracteres invisibles: `^I` es un tabulador y `$` es el fin de línea. |
|
||||||
|
|
||||||
|
## Errores típicos y qué significan
|
||||||
|
|
||||||
|
| Mensaje | Quién lo da | Causa |
|
||||||
|
|---|---|---|
|
||||||
|
| `fichero.c:línea:col: error:` | Compilador | Error en el código fuente. |
|
||||||
|
| `undefined reference to 'x'` | Enlazador (`ld`, `collect2`) | Un `U` sin ninguna `T`: falta pasar un `.o` o una librería. |
|
||||||
|
| `multiple definition of 'x'` | Enlazador | Dos `T` con el mismo nombre. A menudo, un cuerpo de función en un `.h`. |
|
||||||
|
| `missing separator` | Make | La receta empieza por espacios en vez de tabulador. |
|
||||||
|
| `'x' is up to date` | Make | No es un error: el objetivo es más nuevo que todos sus prerrequisitos. Si `x` es una acción (`clean`), falta en `.PHONY`. |
|
||||||
|
| `no newline at end of file` | Clang | El fichero no termina en `\n`. |
|
||||||
|
|
||||||
|
## Go equivalente
|
||||||
|
|
||||||
|
| Go | C |
|
||||||
|
|---|---|
|
||||||
|
| `go build -x` | Ver los comandos que se ejecutan por dentro. |
|
||||||
|
| `go build -work` | Conservar los intermedios (como `-save-temps`). |
|
||||||
|
| `go build -ldflags="-s -w"` | Quitar símbolos (como `strip`). |
|
||||||
@@ -8,4 +8,4 @@ int main(void) {
|
|||||||
printf("%s\n", version);
|
printf("%s\n", version);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
+1
-1
@@ -3,4 +3,4 @@
|
|||||||
|
|
||||||
const char *escpos_version(void);
|
const char *escpos_version(void);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -3,7 +3,3 @@
|
|||||||
const char *escpos_version(void) {
|
const char *escpos_version(void) {
|
||||||
return "1.0.0";
|
return "1.0.0";
|
||||||
}
|
}
|
||||||
|
|
||||||
static int some_function(void) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user