¿Por qué C tendría “matrices falsas”? [closed]

10 minutos de lectura

avatar de usuario
Burik

Estoy leyendo El manual de los enemigos de Unix y en el capítulo 9 hay algo que realmente no entiendo:

C realmente tampoco tiene arreglos. Tiene algo que parece una matriz pero en realidad es un puntero a una ubicación de memoria.

Realmente no puedo imaginar ninguna forma de almacenar una matriz en la memoria que no sea usar punteros para indexar ubicaciones de memoria. ¿Cómo C implementa arreglos “falsos” de todos modos? ¿Hay alguna veracidad en esta afirmación?

  • Qué montón de tonterías. Por supuesto que c tiene arreglos; y la mayoría de las variables tienen una ubicación en la memoria … es solo que puede usar la aritmética de punteros para acceder a los elementos de la matriz, eso es todo.

    – Betsabé

    14 de enero de 2017 a las 16:14


  • Creo que el autor solo estaba tratando de ser sarcástico y no esperaba que nadie lo cuestionara.

    – Dei Dei

    14 de enero de 2017 a las 16:15

  • Realmente creo que el autor es un poco pavo.

    – Betsabé

    14 de enero de 2017 a las 16:16

  • Posiblemente un poco amplio como una pregunta. Otros lenguajes mantienen efectivamente la información de forma sobre la matriz en una estructura para que el tiempo de ejecución del lenguaje sepa cosas como el número de dimensiones, los límites inferior y superior de cada dimensión de la matriz y cualquier otra cosa que se necesite, así como el puntero al inicio de la matriz. datos que almacenan la matriz.

    –Jonathan Leffler

    14 de enero de 2017 a las 16:16

  • El manual UNIX-HATERS es una lectura interesante. Fue publicado en 1994. Describe una era antes de que los estándares POSIX ayudaran a unificar la forma en que se comportaban los sistemas Unix. Mucho de lo que se queja está arreglado. Pero también hubo algo de justicia en muchas de las quejas.

    –Jonathan Leffler

    14 de enero de 2017 a las 16:24

avatar de usuario
zoul

Creo que el punto del autor es que las matrices C son realmente solo una fina capa de aritmética de punteros. El operador subíndice se define simplemente como a[b] == *(a + b)para que puedas decir fácilmente 5[a] en vez de a[5] y hacer otras cosas horribles como acceder a la matriz más allá del último índice.

En comparación con eso, una “matriz verdadera” sería aquella que conoce su propio tamaño, no le permite hacer aritmética de punteros, acceder más allá del último índice sin un error o acceder a su contenido usando un tipo de elemento diferente. En otras palabras, una “matriz verdadera” es una abstracción estricta que no lo vincula a una sola representación; en su lugar, podría ser una lista vinculada, por ejemplo.

PD. Para ahorrarme algunos problemas: realmente no tengo una opinión sobre esto, solo estoy explicando la cita del libro.

  • Hmm, entonces, ¿se permite que las matrices tengan acceso a elementos O (n) y aún se llamen matrices? Eso irrita un poco.

    – Acercándose a la OscuridadPez

    14 de enero de 2017 a las 21:26

  • @ApproachingDarknessFish ¿De dónde sacaste O(n) ¿de?

    – Alejandro

    15 de enero de 2017 a las 0:11

  • @ApproachingDarknessFish En cualquier caso, cuando veo “lista enlazada”, leo “pérdida de caché y terror de rendimiento” y corro muy, muy lejos

    – Alejandro

    15 de enero de 2017 a las 0:16

  • Esta respuesta es incorrecta en varios niveles. Una matriz en C lo hace conocer su propio tamaño. ¿Por qué le impediría hacer aritmética de punteros? C no le impide hacer aritmética de punteros en cualquier puntero si así lo desea; el lenguaje está diseñado con la filosofía de que sabes lo que estás haciendo y se te debe permitir hacerlo. Y la afirmación de que una matriz se puede implementar como una lista enlazada es una gran tontería, como ya se ha señalado. Si hay un lenguaje que lo hace, ¡sin duda no sería el C!

    – Cody gris

    15 de enero de 2017 a las 10:21


  • Casi lo único que tienes aquí es la primera oración. Sí, el autor probablemente esté tratando de decir que las matrices C son poco más que una fina capa de aritmética de punteros. Pero también hay muchas cosas en C, por lo que se necesita más explicación. Eso no significa que las matrices no sean una verdadera entidad de primera clase en el lenguaje, lo son. Los principiantes a menudo confunden las matrices y los punteros, y el lenguaje en muchos casos parece confundirlos, pero son diferentes. Lo que está diciendo sobre el operador de subíndice no tiene nada que ver en particular con las matrices; es solo la sintaxis formal del [] operador.

    – Cody gris

    15 de enero de 2017 a las 10:23


avatar de usuario
usuario268396

Hay una diferencia entre las matrices C y los punteros, y se puede ver en la salida de sizeof() expresiones Por ejemplo:

void sample1(const char * ptr)
{
   /* s1 depends on pointer size of architecture */
   size_t s1 = sizeof(ptr); 
}
size_t sample2(const char arr[])
{
   /* s2 also depends on pointer size of architecture, because arr decays to pointer */
   size_t s2 = sizeof(arr); 
   return s2;
}
void sample3(void)
{
   const char arr[3];
   /* s3 = 3 * sizeof(char) = 3 */
   size_t s2 = sizeof(arr); 
}
void sample4(void)
{
   const char arr[3];
   /* s4 = output of sample2(arr) which... depends on pointer size of architecture, because arr decays to pointer */
   size_t s4 = sample2(arr); 
}

los sample2 y sample4 en particular, es probablemente la razón por la que las personas tienden a combinar matrices C con punteros C, porque en otros lenguajes simplemente puede pasar matrices como argumento a una función y hacer que funcione ‘igual que’ como lo hizo en la función de llamada. Del mismo modo, debido a cómo funciona C, puede pasar punteros en lugar de matrices y esto es ‘válido’, mientras que en otros lenguajes con una distinción más clara entre matrices y punteros no lo sería.

También podrías ver la sizeof() salida como consecuencia de la semántica de paso por valor de C (ya que las matrices de C se descomponen en punteros).

Además, algunos compiladores también admiten esta sintaxis de C:

void foo(const char arr[static 2])
{
   /* arr must be **at least** 2 elements in size, cannot pass NULL */
}

  • La sintaxis de este último es C estándar. Sin embargo, no es muy conocida y aún menos comprendida.

    – demasiado honesto para este sitio

    14 de enero de 2017 a las 16:58

  • @Olaf Lo sé, pero entre la edad de la Manual de los enemigos de Unix y la falta de soporte para el C moderno en general por parte de algunos (extrañamente) populares compiladores de C (msvc) Sentí que era mejor no insistir demasiado en el tema aquí…

    – usuario268396

    14 de enero de 2017 a las 17:03

  • Afortunadamente, los compiladores típicos de Unix como gcc y clang no insisten en seguir con una versión obsoleta del estándar desde hace 18 años. Por lo tanto, debemos suponer que para una pregunta de Unix, C moderno está disponible (y también para impulsar la eventual eliminación de esos legados).

    – demasiado honesto para este sitio

    14 de enero de 2017 a las 17:06

  • @Olaf Interesante. Mientras que la sintaxis es entendida por gcc -std=c11 -Wall -Wextrano parece quejarse cuando paso un const char buf[2] o 0 a una función con const char buf[static 4]. ¿Qué me estoy perdiendo? Actualizar: clang -std=c11 advierte :/

    – Jonas Schäfer

    15 de enero de 2017 a las 10:20


  • @Ruslan Por supuesto, puedo transmitir para decirle al compilador que estoy seguro de que estoy haciendo lo correcto.

    – Jonas Schäfer

    15 de enero de 2017 a las 10:33

La declaración que citó es objetivamente incorrecta. Las matrices en C no son punteros.

La idea de implementar matrices como punteros se usó en los lenguajes B y BCPL (ancestros de C), pero no ha sobrevivido a la transición a C. En las primeras edades de C, la “compatibilidad con versiones anteriores” con B y BCPL se consideró algo importante, lo que es por eso que C se ordena de cerca emular el comportamiento de las matrices B y BCPL (es decir, las matrices C “decaen” fácilmente en punteros). Sin embargo, las matrices C no son “punteros a una ubicación de memoria”.

La cita del libro es completamente falsa. Este concepto erróneo está bastante extendido entre los novatos de C. Pero cómo se las arregló para entrar en un libro está más allá de mí.

  • Te perdiste el contexto de lo que es el libro. Es un acto épico de troleo, no un libro serio destinado a enseñar cosas objetivamente correctas sobre C y Unix.

    – R.. GitHub DEJA DE AYUDAR A ICE

    15 de enero de 2017 a las 14:52

avatar de usuario
ocultar

El autor probablemente quiere decir que las matrices están restringidas de manera que las hacen sentir como ciudadanos de segunda clase desde el punto de vista del programador. Por ejemplo, dos funciones, una está bien, otra no:

int finefunction() {
    int ret = 5;
    return ret;
}

int[] wtffunction() {
    int ret[1] = { 5 };
    return ret;
}

Puede solucionar esto un poco envolviendo arreglos en estructuras, pero solo enfatiza que los arreglos son diferentes, no son como otros tipos.

struct int1 {
    int a[1];
}

int[] finefunction2() {
    struct int1 ret = { { 5 } };
    return ret;
}

Otro efecto de esto es que no puede obtener el tamaño de la matriz en tiempo de ejecución:

int my_sizeof(int a[]) {
    int size = sizeof(a);
    return size;
}

int main() {
    int arr[5];
    // prints 20 4, not 20 20 as it would if arrays were 1st class things
    printf("%d %d\n", sizeof(arr), my_sizeof(arr)); 
}

Otra forma de decir lo que dicen los autores es que, en la terminología de C (y C++), “matriz” significa algo más que en la mayoría de los demás lenguajes.


Entonces, su pregunta de título, ¿cómo se almacenaría una “matriz verdadera” en la memoria? Bueno, no hay un solo tipo de “matriz verdadera”. Si desea matrices verdaderas en C, básicamente tiene dos opciones:

  1. Use calloc para asignar el búfer y almacene el puntero y el recuento de elementos aquí

    struct intarrayref {
      size_t count;
      int *data;
    }
    

    Esta estructura es básicamente una referencia a la matriz, y puede pasarla muy bien a funciones, etc. Querrá escribir funciones para operar en ella, como crear una copia de los datos reales.

  2. Usar miembro de matriz flexibley asigne toda la estructura con una sola llamada

    struct intarrayobject {
        size_t count;
        int data[];
    }
    

En este caso, asigna tanto los metadatos (count), y el espacio para los datos de la matriz de una sola vez, pero el precio es que ya no puede pasar esta estructura como valor, porque eso dejaría atrás los datos adicionales. Tiene que pasar el puntero a esta estructura a las funciones, etc. Por lo tanto, es una cuestión de opinión si uno consideraría esto como una “matriz verdadera” o simplemente una matriz C normal ligeramente mejorada.

Como todo el libro, es un caso de troleo, específicamente, el tipo de troleo que consiste en afirmar algo casi cierto pero incorrecto para solicitar respuestas airadas sobre por qué está mal. C ciertamente tiene arreglos/tipos de arreglos reales, como lo demuestra la forma en que funcionan los tipos de puntero a arreglo (y los arreglos multidimensionales).

  • Hay una línea muy fina, y tradicionalmente (especialmente en el momento en que se escribió el libro) trolear significaba algo que pretendía ser cómico y divertido, muy parecido a la sátira. Fue mucho más tarde que la palabra fue reapropiada como eufemismo para acoso, discurso de odio, etc.

    – R.. GitHub DEJA DE AYUDAR A ICE

    15 de enero de 2017 a las 22:32

  • Hay una línea muy fina, y tradicionalmente (especialmente en el momento en que se escribió el libro) trolear significaba algo que pretendía ser cómico y divertido, muy parecido a la sátira. Fue mucho más tarde que la palabra fue reapropiada como eufemismo para acoso, discurso de odio, etc.

    – R.. GitHub DEJA DE AYUDAR A ICE

    15 de enero de 2017 a las 22:32

¿Ha sido útil esta solución?