progress
This commit is contained in:
@@ -52,6 +52,30 @@ propios gestores sobre `mmap` y pueden no tener `[heap]` en absoluto.
|
||||
Comprobado con el proceso principal de VS Code: tiene `[stack]` pero no
|
||||
`[heap]`.
|
||||
|
||||
### Ejemplo: dónde vive cada parte del buffer
|
||||
|
||||
Medido con gdb en `hello` (Fedora, 2026-10-06), en el punto de parada de
|
||||
`main`:
|
||||
|
||||
| Qué | Expresión en gdb | Dirección | Zona |
|
||||
|---|---|---|---|
|
||||
| El papel: la variable local `buffer` de `main` | `p &buffer` | `0x7fffffffd520` | `[stack]` |
|
||||
| La casa: el struct de 24 bytes | `p buffer` | `0x4052b0` | `[heap]` |
|
||||
| Los bytes: el bloque de 128 | `p buffer->data` | `0x4052d0` | `[heap]` |
|
||||
|
||||
- Son **tres sitios**: el puntero está en la pila, y los dos bloques de
|
||||
`malloc`/`realloc` están en el montón, separados entre sí.
|
||||
- Los campos del struct sí van juntos y en orden: `&buffer->len` es
|
||||
`0x4052b8`, 8 bytes después de `data`.
|
||||
- El struct y los datos están a 32 bytes y no a 24: `malloc` redondea el tamaño
|
||||
y guarda delante de cada bloque una cabecera con información para `free`.
|
||||
- `info proc mappings` en gdb (o `/proc/<pid>/maps`) dice a qué zona pertenece
|
||||
cada dirección.
|
||||
|
||||
**En Go** la variable `b := &Buffer{}` también puede vivir en la pila y el
|
||||
struct en el montón, pero lo decide el compilador (*escape analysis*) y no lo
|
||||
ves.
|
||||
|
||||
### Memoria virtual frente a memoria real
|
||||
|
||||
`/proc/<pid>/status` muestra dos cifras:
|
||||
@@ -243,6 +267,39 @@ Comparado con el `copy(dst, src)` de Go:
|
||||
- Go copia `min(len(dst), len(src))` y devuelve cuántos copió: nunca se sale.
|
||||
`memcpy` no puede comprobar nada porque no conoce los tamaños.
|
||||
|
||||
### `restrict`: la promesa de que no se solapan
|
||||
|
||||
Desde C99 la firma real lleva `restrict`:
|
||||
|
||||
```c
|
||||
void *memcpy(void *restrict dst, const void *restrict src, size_t n);
|
||||
```
|
||||
|
||||
`restrict` en un puntero es una **promesa del programador al compilador**:
|
||||
mientras dura la función, a la memoria que se toca por ese puntero no se accede
|
||||
por ningún otro. Aquí quiere decir que `dst` y `src` no se solapan.
|
||||
|
||||
- **Para qué sirve:** sabiendo que no se solapan, el compilador puede copiar en
|
||||
bloques de 16 o 32 bytes, reordenar y no volver a leer `src` después de cada
|
||||
escritura en `dst`. Sin la promesa, tendría que suponer que escribir en `dst`
|
||||
puede cambiar `src`.
|
||||
- **Quién lo comprueba:** nadie. Si rompes la promesa (con bloques que se
|
||||
solapan), es comportamiento indefinido. Para bloques solapados está `memmove`,
|
||||
que no lleva `restrict`.
|
||||
- **Para quien llama no cambia nada:** se llama igual que en C89.
|
||||
|
||||
`man 3 memcpy` en Fedora lo escribe como `void dest[restrict .n]`. Es una
|
||||
notación de la página de manual para decir que el bloque mide `n`. No es C
|
||||
válido.
|
||||
|
||||
Versión del estándar: el `Makefile` compila con `-std=c17`, así que
|
||||
`restrict` existe. Para comprobar qué estándar usa el compilador:
|
||||
`echo | gcc -std=c17 -dM -E - | grep __STDC_VERSION__` da `201710L` (C17). Sin
|
||||
`-std`, gcc 15 usa C23 (`202311L`).
|
||||
|
||||
**En Go** no existe. El compilador tiene que suponer lo peor cuando dos slices
|
||||
pueden compartir memoria, y `copy` funciona aunque se solapen, como `memmove`.
|
||||
|
||||
**Por qué usarlo en vez de un bucle:** glibc implementa `memcpy` en ensamblador
|
||||
optimizado, con instrucciones que copian 16 o 32 bytes de una vez (SIMD). A
|
||||
veces gcc incluso convierte un bucle de copia en una llamada a `memcpy`. Y
|
||||
|
||||
Reference in New Issue
Block a user