Por que C no te impedirá cometer errores



En resumen: porque así lo dijimos.



:)



Muy bien, esta es una explicación demasiado corta para un artículo, querido lector, y mis palabras provocativas requieren una explicación.



La reunión del comité de lenguaje C, que originalmente se planeó para celebrarse en Friburgo, Alemania, pero no creció juntos por razones obvias, terminó el 7 de agosto. Ha ido bien, avanzamos en todos los frentes. Sí, de hecho estamos progresando, se lo aseguro, y C no está muerto.



También mencionaré que me convertí en el editor del proyecto C, así que antes de tomar el título como una declaración ignorante de alguien que es demasiado perezoso para "tratar de mejorar", quiero asegurarles que en realidad estoy trabajando muy duro para asegurar que C pueda para satisfacer las necesidades de los desarrolladores sin tener que atornillar 50 extensiones específicas en aras de construir bibliotecas y aplicaciones más o menos hermosas y útiles.



Y, sin embargo, lo dije (que el lenguaje C nunca evitará que cometas errores), lo que significa que tengo que justificarlo. Podemos ver miles de CVE y tickets asociados con un montón de código C, o podemos hacer que MISRA verifique enérgicamente cada característica de C en busca de un posible mal uso ( hola, declaraciones de prototipos de K&R...) o tener errores más complejos y divertidos relacionados con la portabilidad y el comportamiento indefinido. Pero, en cambio, leemos la fuente original: lo que dijo el propio Comité.



Oh, ¿es hora de conseguir palomitas de maíz?



No, querido lector, deja a un lado las palomitas. Al igual que con todos los procedimientos ISO, no puedo citar las palabras de nadie más, y este artículo no pretende avergonzar a nadie. Pero explicaré por qué nunca se descartará algo que fácilmente podríamos considerar un mal comportamiento en un documento ISO C compatible con los estándares. Y comencemos con el documento del Dr. Philipp Klaus Krause:



N2526, use const para los datos de la biblioteca que no se modificarán .



N2526 es un documento muy simple.



, , , . — , ! …


Estoy de acuerdo, no es exactamente lo mismo, pero estoy seguro de que la idea te parecerá razonable, querido lector. Cuando se sometió a votación este documento, casi no hubo votos en contra. Más tarde, varias personas se opusieron enérgicamente porque esta propuesta rompía el antiguo código. Por supuesto, esto es malo: ¿hasta yo me quedo sin aliento cuando pienso en sumar const? No hay ABI en el lenguaje C que pueda verse influenciado por la innovación. C (su implementación) ni siquiera presta atención a los calificadores, ¡¿cómo podemos romper algo?! Echemos un vistazo a por qué algunas personas piensan que este será un cambio radical.



Lengua C



O, como me gusta llamarlo, "La seguridad de tipos es para los idiomas defectuosos". Sí, demasiado detallado, así que detengámonos en "C". Quizás se pregunte por qué digo que lenguajes como C no son seguros para los tipos. Después de todo, aquí está:



struct Meow {
    int a;
};

struct Bark {
    double b;
    void* c;
};

int main (int argc, char* argv[]) {
    (void)argc;
    (void)argv;

    struct Meow cat;
    struct Bark dog = cat;
    // error: initializing 'struct Bark' with an expression of incompatible type 'struct Meow'

    return 0;
}


Para ser honesto, ¡esto me parece una fuerte seguridad, Jim! Y así todo se vuelve aún más picante:



#include <stdlib.h>

struct Meow {
    int a;
};

struct Bark {
    double b;
    void* c;
};

int main (int argc, char* argv[]) {
    (void)argc;
    (void)argv;

    struct Meow* p_cat = (struct Meow*)malloc(sizeof(struct Meow));
    struct Bark* p_dog = p_cat;
    // :3
    
    return 0;
}


Sí, el estándar C permite que dos tipos de punteros completamente independientes se refieran entre sí. La mayoría de los compiladores le advertirán sobre esto, pero el estándar requiere que acepte este código a menos que lo desenrolle -Werror -Wall -Wpedantic, etc., etc., etc.



De hecho, el compilador puede aceptar esto sin conversión explícita:



  • volatile (¡¿Quién necesita esta semántica?!)
  • const (¡escriba aquí cualquier dato de solo lectura!)
  • _Atomic (seguridad de hilo!)


No estoy diciendo que no debería poder hacer todo esto. Pero cuando escribes en C, en el que es muy fácil crear una función de 500-1000 líneas con nombres de variables completamente incomprensibles, Infa Sotka, trabajas principalmente con punteros, y generalmente carece de seguridad en términos del lenguaje base. Nota: esto viola las restricciones, pero ya se ha escrito tanto código antiguo que cada implementación de alguna manera ignora los calificadores y, debido a esto, no se impedirá que su código se compilegracias @fanf!)! En esta situación, es posible con el compilador identificar fácilmente cada falla potencial, y recibirá advertencias, pero no será necesario encasillar para que el compilador sepa lo que realmente quería hacer. Sin embargo, lo que es más importante, los seres humanos que vienen después de ti tampoco entenderán lo que te propusiste hacer.



Todo lo que tiene que hacer es eliminar la función -Werror -Wall -Wpedanticy estará listo para cometer los delitos de multiproceso, modo de solo lectura y registros de hardware.



Todo es justo ahora, ¿verdad? Si alguien elimina todas esas banderas de advertencia y error, no le importará qué tipo de pasos en falso o estupideces hagas. Esto significa que al final, estas advertencias son completamente irrelevantes e inofensivas en lo que respecta al cumplimiento de ISO C. Y sin embargo ...



Estamos considerando romper las advertencias



Si.



Este es un infierno especial al que están acostumbrados los desarrolladores de C y, en menor medida, los desarrolladores de C ++. Las advertencias son molestas y, como muestra la práctica, incluso -Weverythingo /W4muy molestas. Ocultar advertencias de variables en el espacio de nombres global (gracias, ahora todos los encabezados y bibliotecas de C son un problema), usar nombres "reservados" (como dicen los niños, " lol nice one xd !! "), y "esta estructura tiene relleno porque usaste alignof"(... sí, sí, sé que tiene relleno, pedí explícitamente más relleno, PORQUE YO UTILIZÉ alignof, SR. COMPILATOR) - todo esto lleva mucho tiempo.



Pero estas son advertencias.



Incluso si son molestos, te ayudan a evitar problemas. El hecho de que pueda ignorar descaradamente todos los calificadores y descuidar todo tipo de seguridad de lectura, escritura, transmisión y solo lectura es un problema importante cuando se trata de comunicar mis intenciones y evitar errores. Incluso la antigua sintaxis de K&R provocaba errores en las bases de código industriales y gubernamentales porque los usuarios estaban haciendo algo mal. Y lo hicieron no porque fueran pésimos programadores, sino porque trabajan con bases de código que a menudo son más antiguas que ellos y se están preparando para luchar con ellas. largos millones de líneas. Es imposible mantener todo el código base en su cabeza: las convenciones, el análisis estático, las advertencias de alto nivel y otras herramientas son para esto. Desafortunadamente,



todos quieren tener un código sin advertencias.



Esto significa que cuando el desarrollador de GCC hace que las advertencias sean más sensibles a situaciones potencialmente problemáticas, los mantenedores (no los desarrolladores originales) reciben inesperadamente registros en negrita de varios gigabytes del código anterior, que contienen muchas advertencias nuevas y todo tipo de cosas diferentes. "Esto es una idiotez", dicen, "el código funcionó durante AÑOS, entonces, ¿por qué GCC se queja ahora?" Es decir, incluso si agrega constfunciones a la firma, incluso si es moral, espiritual y realmente correcta, se evitará. "Romper" a la gente significa "ahora tienen que buscar un código que tenga intenciones dudosas". Este es un código que puede, bajo pena de un comportamiento indefinido, destruir su chip o dañar su memoria.... Pero este es otro problema que acompaña a la profesión de desarrollador C en estos días.



La edad como medida de calidad



¿Cuántas personas asumieron incluso que sudotenían una vulnerabilidad tan primitiva como "-1 o un desbordamiento de enteros da acceso a todo"? ¿Cuántas personas pensaron que Heartbleed podría ser un problema real? ¿Cuántos desarrolladores de juegos envían bibliotecas stb “pequeñas” sin siquiera usar un phaser y sin darse cuenta de que estas bibliotecas contienen vulnerabilidades de entrada más importantes de lo que imagina? No estoy criticando todos estos desarrollos ni a sus autores: nos brindan una ayuda vital, de la que el mundo dependió durante décadas, a menudo con poco o ningún apoyo hasta que surge algún gran problema. Pero las personas que idolatran estos desarrollos y los ponen por su cuenta, luego exudan un dicho venenoso de ellos mismos, que ilustra el error del sobreviviente:



, ?


Manteniendo los principios de compatibilidad con versiones anteriores y "relajados" como los más altos ideales de C, las personas que sobreviven lo suficiente en esta industria comienzan a equiparar la edad con la calidad, como si las bases de código fueran barriles de vino. Cuanto más antiguo y prolongado se utilice el código, más fino y refinado será el vino.



Desafortunadamente, todo no es tan romántico y lindo: lleno de errores, con una gran cantidad de agujeros de seguridad, toda esta deuda técnica se vuelve más peligrosa cada día. Con el tiempo, todos los sistemas se convierten en pilas podridas, descuidadas y parcialmente sin apoyo. Están embellecidos y se les da un espíritu de nobleza, pero en realidad son momias que están esperando que las pinchen con torpeza, y luego sus abscesos antediluvianos supurantes explotarán e inundarán su aplicación con su hermoso botulismo envejecido.



Hmm ... asqueroso. Pero, ¿qué pasa con el estándar C?



El problema que noté durante mi (increíblemente breve) mandato como participante de la reunión es que damos prioridad a la compatibilidad con versiones anteriores. Para aquellos que incluso están migrando a C hoy, nos aferramos a las aplicaciones antiguas y sus casos de uso y nos privamos de la oportunidad de mejorar la seguridad, la protección o el arte del código C. , puede apagarlos. Estas advertencias, no errores, no son en vano: una máquina C abstracta no requierePara realizar diagnósticos, ISO C permite que dicho código sea aceptado en modos de ensamblaje estrictos. Y eso ayudaría a todo el mundo a romper con las API que afirman abiertamente que "cambiar el contenido que le proporcionamos es un comportamiento indefinido".



Sin embargo, cambiamos de opinión sobre este documento después de que se diera el motivo de que "no podemos introducir nuevas advertencias".



El argumento en contra de la propuesta fue: "Hay mucho código escrito que se romperá si cambiamos estas firmas". Esto, nuevamente, limita los cambios a las advertencias en términos de comportamiento de ruptura (recuerde, conversiones implícitas que eliminan los calificadores, incluso_Atomic- son plenamente válidos según ISO C, incluso si violan las restricciones). Si ese fuera el caso, cada autor del compilador introduciría algo como Ages in Rust, solo para advertencias, para brindar a las personas un punto de referencia "estable" para las pruebas. Esta propuesta no es nueva: he leído documentos similares de ingenieros de Coverity sobre la generación de nuevas alertas y cómo reaccionan los usuarios a ellas. Es difícil gestionar la "confianza" de los desarrolladores sobre nuevas advertencias y otras cosas. Se necesita mucho tiempo para convencer a la gente de su utilidad. Incluso John Carmack tuvo que trabajar duro para obtener el conjunto correcto de advertencias y errores de sus herramientas de análisis estático para adaptarse a su desarrollo, antes de concluir que era "irresponsable no usar esto" .



Y, sin embargo, nosotros, el Comité, no acordamos agregar constfunciones de devolución de valor a las cuatro funciones porque agregaría advertencias al código potencialmente peligroso. Nos opusimos a que se desaprobara la antigua sintaxis K&R, a pesar de la fuerte evidencia de descuidos inocentes y vulnerabilidades graves al pasar los tipos incorrectos. Casi hemos agregado un comportamiento indefinido al preprocesador, solo para bajarlo, pero para hacer que la implementación de C "se comporte como debería". Debido a la compatibilidad con versiones anteriores, siempre caminamos al límite para evitar cometer errores obvios. Y esto, querido lector, es lo que más me asusta sobre el futuro de S.



El estándar C no te protege



No se equivoque: no importa lo que los programadores le digan o lo que le susurren. El comité directivo de lenguaje C es muy claro. No agregaremos nuevas advertencias a su código anterior, incluso si este código puede ser peligroso. No le impediremos cometer errores, porque puede socavar la idea de cómo funciona su código anterior, lo cual es incorrecto. No ayudaremos a los novatos a escribir un mejor código C. No requeriremos que su código anterior cumpla con ningún estándar. Cada nueva característica será opcional porque no podemos imaginarnos forzar a los autores de compiladores a apegarse a un estándar más alto o esperar más de nuestros desarrolladores de bibliotecas estándar.



Dejaremos que el compilador te mienta. Le mentiremos a su código. Y cuando todo salga mal, habrá un error, "Oh, pasó algún tipo de basura", habrá una fuga de datos, sacudiremos solemnemente la cabeza. Compartiremos nuestras ideas, oraremos por usted y diremos: "Bueno, qué vergüenza". En efecto, vergüenza ...



Quizás algún día lo arreglemos, querido lector.



All Articles