Los 10 errores principales de los proyectos de C ++ encontrados en 2018

Han pasado tres meses desde que termin√≥ 2018. Para muchos, simplemente pas√≥ volando, pero para nosotros, los desarrolladores de PVS-Studio, fue un a√Īo bastante agitado. Est√°bamos trabajando arduamente, compitiendo sin miedo por correr la voz sobre el an√°lisis est√°tico y est√°bamos buscando errores en proyectos de c√≥digo abierto, escritos en lenguaje C, C ++, C # y Java. ¬°En este art√≠culo, reunimos los 10 mejores m√°s interesantes para usted!


Para encontrar los lugares más interesantes, utilizamos el analizador de código estático PVS-Studio . Puede detectar errores y vulnerabilidades potenciales en el código, escrito en los idiomas enumerados anteriormente.

Si est√° entusiasmado con la b√ļsqueda de errores usted mismo, siempre puede descargar y probar nuestro analizador. Ofrecemos la versi√≥n de analizador gratuita para estudiantes y desarrolladores entusiastas, la licencia gratuita para desarrolladores de proyectos de c√≥digo abierto y tambi√©n la versi√≥n de prueba para todo el mundo y su perro. Qui√©n sabe, ¬Ņquiz√°s para el pr√≥ximo a√Īo podr√°s crear tu propio top 10? :)

Nota: lo invito a que se revise a s√≠ mismo y, antes de mirar la advertencia del analizador, intente revelar los defectos usted mismo. ¬ŅCu√°ntos errores podr√°s encontrar?

Décimo lugar


Fuente: Into Space Again: cómo el Unicornio visitó Stellarium

Este error se detectó al verificar un planetario virtual llamado Stellarium.

El fragmento de c√≥digo anterior, aunque peque√Īo, est√° plagado de un error bastante complicado:

Plane::Plane(Vec3f &v1, Vec3f &v2, Vec3f &v3) : distance(0.0f), sDistance(0.0f) { Plane(v1, v2, v3, SPolygon::CCW); } 

¬ŅLo encontraste?

Advertencia de PVS-Studio : V603 El objeto se creó pero no se está utilizando. Si desea llamar al constructor, debe usar 'this-> Plane :: Plane (....)'. Plane.cpp 29

El autor del código tenía la intención de inicializar los campos de algunos objetos, utilizando otro constructor, anidado en el principal. Bueno, en lugar de eso, solo logró crear un objeto temporal destruido al salir de su alcance. Al hacerlo, los campos de varios objetos permanecerán sin inicializar.

El autor debería haber utilizado un constructor delegado, introducido en C ++ 11, en lugar de una llamada de constructor anidada. Por ejemplo, podría haber escrito así:

 Plane::Plane(Vec3f& v1, Vec3f& v2, Vec3f& v3) : Plane(v1, v2, v3, SPolygon::CCW) { distance = 0.0f; sDistance = 0.0f; } 

De esta manera, todos los campos necesarios se habr√≠an inicializado correctamente. ¬ŅNo es maravilloso?

Noveno lugar


Fuente: Perl 5: Cómo ocultar errores en macros

Una macro muy notable se destaca en toda su belleza en el noveno lugar.

Al recopilar errores para escribir un artículo, mi colega Svyatoslav encontró una advertencia, emitida por el analizador, que se relacionaba con el uso de macros. Aquí esta:

 PP(pp_match) { .... MgBYTEPOS_set(mg, TARG, truebase, RXp_OFFS(prog)[0].end); .... } 

Para descubrir qué estaba mal, Svyatoslav cavó más profundo. Abrió la definición de macro y vio que contenía varias macros anidadas, algunas de las cuales a su vez también tenían macros anidadas. Era muy difícil de entender, así que tuvo que usar un archivo preprocesado. Lamentablemente, no ayudó. Esto es lo que Svyatoslav encontró en la línea de código anterior:

 (((targ)->sv_flags & 0x00000400) && (!((targ)->sv_flags & 0x00200000) || S_sv_only_taint_gmagic(targ)) ? (mg)->mg_len = ((prog->offs)[0].end), (mg)->mg_flags |= 0x40 : ((mg)->mg_len = (((targ)->sv_flags & 0x20000000) && !__builtin_expect(((((PL_curcop)->cop_hints + 0) & 0x00000008) ? (_Bool)1 :(_Bool)0),(0))) ? (ssize_t)Perl_utf8_length( (U8 *)(truebase), (U8 *)(truebase)+((prog->offs)[0].end)) : (ssize_t)((prog->offs)[0].end), (mg)->mg_flags &= ~0x40)); 

Advertencia de PVS-Studio : V502 Quiz√°s el operador '?:' Funciona de una manera diferente a la esperada. El operador '?:' Tiene una prioridad menor que el operador '&&'. pp_hot.c 3036

Creo que ser√≠a un desaf√≠o simplemente notar ese error. Hemos estado pensando en este c√≥digo durante mucho tiempo, pero, francamente, no hemos encontrado ning√ļn error. De todos modos, es un ejemplo bastante divertido de c√≥digo mal legible.

Dicen que las macros son malvadas. Claro, hay casos en que las macros son indispensables, pero si puede reemplazar una macro con una función, definitivamente debería hacerlo.

Las macros anidadas están especialmente llenas de trampas. No solo porque es difícil darles sentido, sino también porque pueden dar resultados impredecibles. Si un programador comete un error en tal macro, será mucho más difícil encontrarlo en una macro que en una función.

Octavo lugar


Fuente: cromo: otros errores

El siguiente ejemplo fue tomado de la serie de artículos sobre el análisis del proyecto Chromium. El error se estaba ocultando en la biblioteca WebRTC.

 std::vector<SdpVideoFormat> StereoDecoderFactory::GetSupportedFormats() const { std::vector<SdpVideoFormat> formats = ....; for (const auto& format : formats) { if (cricket::CodecNamesEq(....)) { .... formats.push_back(stereo_format); } } return formats; } 

Advertencia de PVS-Studio: V789 CWE-672 Los iteradores para el contenedor de 'formatos', utilizados en el bucle basado en rango, se vuelven inválidos cuando se llama a la función 'push_back'. stereocodecfactory.cc 89

El error es que el tama√Īo del vector de formatos var√≠a dentro del rango para el bucle. Los bucles basados ‚Äč‚Äčen el rango se basan en iteradores, es por eso que cambiar el tama√Īo del contenedor dentro de dichos bucles podr√≠a resultar en la invalidaci√≥n de estos iteradores.

Este error persiste si reescribe el ciclo con un uso explícito de iteradores. Para mayor claridad, puedo citar el siguiente código:

 for (auto format = begin(formats), __end = end(formats); format != __end; ++format) { if (cricket::CodecNamesEq(....)) { .... formats.push_back(stereo_format); } } 

Por ejemplo, cuando se usa el método push_back , puede ocurrir una reasignación de vectores; de esta manera, los iteradores abordarán una ubicación de memoria no válida.

Para evitar tales errores, siga la regla: nunca cambie el tama√Īo de un contenedor dentro de un bucle con condiciones vinculadas a este contenedor. Tambi√©n se relaciona con bucles basados ‚Äč‚Äčen rango y bucles que utilizan iteradores. Le invitamos a leer esta discusi√≥n en StackOverflow que cubre el tema de las operaciones que causan la invalidaci√≥n de los iteradores.

Séptimo lugar


Fuente: Godot: sobre el uso regular de analizadores est√°ticos

El primer ejemplo de la industria del juego será un fragmento de código que encontramos en el motor del juego Godot. Probablemente, tomará un poco de trabajo notar el error, pero estoy seguro de que nuestros lectores expertos se encargarán de eso.

 void AnimationNodeBlendSpace1D::add_blend_point( const Ref<AnimationRootNode> &p_node, float p_position, int p_at_index) { ERR_FAIL_COND(blend_points_used >= MAX_BLEND_POINTS); ERR_FAIL_COND(p_node.is_null()); ERR_FAIL_COND(p_at_index < -1 || p_at_index > blend_points_used); if (p_at_index == -1 || p_at_index == blend_points_used) { p_at_index = blend_points_used; } else { for (int i = blend_points_used - 1; i > p_at_index; i++) { blend_points[i] = blend_points[i - 1]; } } .... } 

Advertencia de PVS-Studio: V621 CWE-835 Considere inspeccionar el operador 'para'. Es posible que el bucle se ejecute incorrectamente o no se ejecute en absoluto. animation_blend_space_1d.cpp 113

Echemos un vistazo de cerca a la condición del bucle. La variable de contador se inicializa con el valor blend_points_used - 1 . Además, a juzgar por dos comprobaciones anteriores (en ERR_FAIL_COND y en if ), queda claro que, en el momento de la ejecución del bucle blend_points_used , blend_points_used siempre será mayor que p_at_index . Por lo tanto, la condición del bucle siempre es verdadera o el bucle no se ejecuta en absoluto.

Si blend_points_used - 1 == p_at_index , el bucle no se ejecuta.

En todos los demás casos, la comprobación i> p_at_index siempre será verdadera, ya que el contador i sube en cada iteración del bucle.

Parece que el ciclo es eterno, pero no es así.

En primer lugar, se producir√° un desbordamiento de enteros de la variable i (que es un comportamiento indefinido). Esto significa que no debemos confiar en ello.

Si no estaba firmado int , entonces, después de que el contador alcanza el mayor valor posible, el operador i ++ lo convertiría en 0 . Tal comportamiento está definido por el estándar y se denomina "ajuste sin firmar". Sin embargo, debe tener en cuenta que el uso de dicho mecanismo tampoco es una buena idea .

¡Era el primer punto, pero todavía tenemos el segundo! El caso es que ni siquiera llegaremos a un desbordamiento de enteros. El índice de la matriz se saldrá de los límites mucho antes. Esto significa que se intentará acceder a la memoria fuera del bloque asignado para la matriz. Lo cual es un comportamiento indefinido también. Un ejemplo clásico :)

Puedo darle un par de recomendaciones para que sea m√°s f√°cil evitar errores similares:

  1. Escribir código simple y comprensible
  2. Revise el código más a fondo y escriba más pruebas para el código recién escrito
  3. Use analizadores est√°ticos;)



Sexto lugar


Fuente: Amazon Lumberyard: A Scream of Anguish

Aquí hay otro ejemplo de la industria de gamedev, es decir, del código fuente del motor AAA de Amazon Lumberyard.

 void TranslateVariableNameByOperandType(....) { // Igor: yet another Qualcomm's special case // GLSL compiler thinks that -2147483648 is // an integer overflow which is not if (*((int*)(&psOperand->afImmediates[0])) == 2147483648) { bformata(glsl, "-2147483647-1"); } else { // Igor: this is expected to fix // paranoid compiler checks such as Qualcomm's if (*((unsigned int*)(&psOperand->afImmediates[0])) >= 2147483648) { bformata(glsl, "%d", *((int*)(&psOperand->afImmediates[0]))); } else { bformata(glsl, "%d", *((int*)(&psOperand->afImmediates[0]))); } } bcatcstr(glsl, ")"); .... } 

Advertencia de PVS-Studio : V523 La declaración 'then' es equivalente a la declaración 'else'. toglsloperand.c 700

Amazon Lumberyard se desarrolla como un motor multiplataforma. Por esta razón, los desarrolladores intentan admitir tantos compiladores como sea posible. Como podemos ver en los comentarios, un programador Igor vino en contra del compilador Qualcomm.

No sabemos si logr√≥ llevar a cabo su tarea y vadear las comprobaciones de compilaci√≥n "paranoicas", pero dej√≥ un c√≥digo muy extra√Īo. Lo extra√Īo es que tanto las ramas de la instrucci√≥n if contienen c√≥digo absolutamente id√©ntico. Lo m√°s probable es que dicho error se haya producido al usar un m√©todo descuidado de copiar y pegar.

Ni siquiera sé qué aconsejar aquí. ¡Así que solo deseo a los desarrolladores de Amazon Lumberyard todo lo mejor para corregir errores y buena suerte para el desarrollador Igor!

Quinto lugar


Fuente: una vez m√°s, el analizador PVS-Studio ha demostrado ser m√°s atento que una persona

Una historia interesante sucedió con el siguiente ejemplo. Mi colega Andrey Karpov estaba preparando un artículo sobre otra verificación del marco Qt. Al escribir algunos errores notables, se topó con la advertencia del analizador, que consideró falsa. Aquí está ese fragmento de código y la advertencia:

 QWindowsCursor::CursorState QWindowsCursor::cursorState() { enum { cursorShowing = 0x1, cursorSuppressed = 0x2 }; CURSORINFO cursorInfo; cursorInfo.cbSize = sizeof(CURSORINFO); if (GetCursorInfo(&cursorInfo)) { if (cursorInfo.flags & CursorShowing) // <= V616 .... } 

Advertencia de PVS-Studio: V616 CWE-480 La constante con nombre 'CursorShowing' con el valor de 0 se utiliza en la operación bit a bit. qwindowscursor.cpp 669

Lo que significa que PVS-Studio se quej√≥ en el lugar, lo que obviamente no tuvo ning√ļn error. Es imposible que la constante CursorShowing sea 0 , ya que solo un par de l√≠neas arriba se inicializan en 1 .

Como Andrey estaba usando una versi√≥n de analizador inestable, cuestion√≥ la exactitud de la advertencia. Mir√≥ cuidadosamente a trav√©s de ese c√≥digo y a√ļn no encontr√≥ un error. Finalmente le dio un falso positivo en el rastreador de errores para que otros colegas pudieran remediar la situaci√≥n.

Solo un an√°lisis detallado mostr√≥ que PVS-Studio result√≥ ser m√°s cuidadoso que una persona nuevamente. El valor 0x1 se asigna a una constante con nombre llamada cursorShowing, mientras que CursorShowing participa en una operaci√≥n "y" bit a bit. Estas son dos constantes totalmente diferentes, la primera comienza con una letra min√ļscula, la segunda, con may√ļscula.

El código se compila correctamente, porque la clase QWindowsCursor realmente contiene una constante con este nombre. Aquí está su definición:

 class QWindowsCursor : public QPlatformCursor { public: enum CursorState { CursorShowing, CursorHidden, CursorSuppressed }; .... } 

Si no asigna un valor a una constante enum explícitamente, se inicializará de manera predeterminada. Como CursorShowing es el primer elemento en la enumeración, se le asignará 0 .

Para evitar tales errores, no debe dar a las entidades nombres demasiado similares. Deber√≠a seguir especialmente esta regla si las entidades son del mismo tipo o se pueden lanzar impl√≠citamente entre s√≠. Como en tales casos, ser√° casi imposible notar el error, pero el c√≥digo incorrecto a√ļn se compilar√° y vivir√° en una calle f√°cil dentro de su proyecto.

Cuarto lugar


Fuente: dispararte en el pie cuando manejes datos de entrada

Nos estamos acercando a los tres primeros finalistas y el siguiente en la línea es el error del proyecto FreeSWITCH.

 static const char *basic_gets(int *cnt) { .... int c = getchar(); if (c < 0) { if (fgets(command_buf, sizeof(command_buf) - 1, stdin) != command_buf) { break; } command_buf[strlen(command_buf)-1] = '\0'; /* remove endline */ break; } .... } 

Advertencia de PVS-Studio: V1010 CWE-20 Los datos contaminados no verificados se usan en el índice: 'strlen (command_buf)'.

El analizador le advierte que algunos datos no verificados se utilizan en la expresi√≥n strlen (command_buf) - 1 . De hecho: si command_buf es una cadena vac√≠a en t√©rminos del lenguaje C (que contiene el √ļnico car√°cter - '\ 0'), strlen (command_buf) devolver√° 0 . En tal caso, se acceder√° a command_buf [-1] , que es un comportamiento indefinido. Eso es malo!

El entusiasmo real de este error no es por qu√© ocurre, sino c√≥mo . Este error es uno de esos ejemplos m√°s bonitos, que "tocas" t√ļ mismo, reproduces. Puede ejecutar FreeSwitch, realizar algunas acciones que conducir√°n a la ejecuci√≥n del fragmento de c√≥digo mencionado anteriormente y pasar una cadena vac√≠a a la entrada del programa.

Como resultado, con un movimiento sutil de la mano, un programa de trabajo se convierte en un no trabajo. Puede encontrar los detalles sobre cómo reproducir este error en el artículo de origen en el enlace que figura más arriba. Mientras tanto, déjame darte un resultado revelador:



Tenga en cuenta que los datos de salida pueden ser cualquier cosa, por lo que siempre debe verificarlos. De esta manera, el analizador no se quejar√° y el programa se volver√° m√°s confiable.

Ahora es el momento de buscar a nuestro ganador: ¬°estamos en el final del juego ahora! Por cierto, los finalistas de errores ya han esperado una larga espera, luego se aburrieron e incluso comenzaron a ser quisquillosos. ¬°Solo mira lo que representaron mientras est√°bamos fuera!



Tercer lugar


Fuente: NCBI Genome Workbench: Investigación científica bajo amenaza

Un fragmento de código del proyecto NCBI Genome Workbench, que es un conjunto de herramientas para estudiar y analizar datos genéticos, abre los 3 ganadores principales. Aunque no es necesario ser un superhumano modificado genéticamente para encontrar este error, solo unas pocas personas conocen la contingencia de cometer un error aquí.

 /** * Crypt a given password using schema required for NTLMv1 authentication * @param passwd clear text domain password * @param challenge challenge data given by server * @param flags NTLM flags from server side * @param answer buffer where to store crypted password */ void tds_answer_challenge(....) { .... if (ntlm_v == 1) { .... /* with security is best be pedantic */ memset(hash, 0, sizeof(hash)); memset(passwd_buf, 0, sizeof(passwd_buf)); ... } else { .... } } 

Advertencias de PVS-Studio:

  • V597 El compilador podr√≠a eliminar la llamada de funci√≥n 'memset', que se utiliza para vaciar el b√ļfer 'hash'. La funci√≥n memset_s () debe usarse para borrar los datos privados. challenge.c 365
  • V597 El compilador podr√≠a eliminar la llamada de funci√≥n 'memset', que se utiliza para vaciar el b√ļfer 'passwd_buf'. La funci√≥n memset_s () debe usarse para borrar los datos privados. challenge.c 366

¬ŅEncontraste un error? Si es as√≠, ¬°eres un ataboy! ... o un superhumano gen√©ticamente modificado.

El hecho es que los compiladores de optimizaci√≥n modernos pueden hacer mucho para permitir que un programa construido funcione m√°s r√°pido. Incluyendo el hecho de que los compiladores ahora pueden rastrear que un b√ļfer, pasado a memset , no se usa en ning√ļn otro lugar.

En este caso, pueden eliminar la llamada "innecesaria" de memset , teniendo todos los derechos para eso. Entonces el b√ļfer que almacena datos importantes puede permanecer en la memoria para el deleite de los atacantes.

En este contexto, este comentario geek "con seguridad es mejor ser pedante" suena a√ļn m√°s divertido. A juzgar por una peque√Īa cantidad de advertencias, dadas para este proyecto, sus desarrolladores hicieron todo lo posible para ser precisos y escribir c√≥digo seguro. Sin embargo, como podemos ver, uno puede f√°cilmente pasar por alto tal defecto de seguridad. De acuerdo con la Enumeraci√≥n de Debilidad Com√ļn, este defecto se clasifica como CWE-14 : Eliminaci√≥n del C√≥digo del Compilador para Borrar Buffers.

Debe usar la función memset_s () para que la desasignación de memoria sea segura. La función es más segura que memset () y no puede ser ignorada por un compilador.

Segundo lugar


Fuente: Cómo PVS-Studio demostró ser más atento que tres programadores y medio

Un medallista de plata nos fue enviado amablemente por uno de nuestros clientes. Estaba seguro de que el analizador emitía algunos falsos positivos.

Evgeniy recibi√≥ el correo electr√≥nico, lo revis√≥ y lo envi√≥ a Svyatoslav. Svyatoslav observ√≥ detenidamente el c√≥digo enviado por el cliente y pens√≥: "¬Ņc√≥mo es posible que el analizador haya cometido un error tan grande?". Entonces fue a pedir consejo a Andrey. Tambi√©n verific√≥ ese lugar y determin√≥: de hecho, el analizador gener√≥ falsos positivos.

Así sigue, eso necesitaba ser reparado. Solo después de que Svyatoslav comenzó a hacer ejemplos sintéticos para crear la tarea en nuestro rastreador de errores, obtuvo lo que estaba mal.

Ninguno de los programadores pudo encontrar los errores, pero realmente estaban en el código. Hablando francamente, el autor de este artículo tampoco logró encontrarlos a pesar del hecho de que el analizador claramente emitió advertencias sobre lugares erróneos.

¬ŅEncontrar√°s un error tan astuto? Ponte a prueba con la vigilancia y la atenci√≥n.


Advertencia de PVS-Studio:
  • V560 Una parte de la expresi√≥n condicional siempre es falsa: (ch> = 0x0FF21). decodew.cpp 525
  • V560 Una parte de la expresi√≥n condicional siempre es verdadera: (ch <= 0x0FF3A). decodew.cpp 525
  • V560 Una parte de la expresi√≥n condicional siempre es falsa: (ch> = 0x0FF41). decodew.cpp 525
  • V560 Una parte de la expresi√≥n condicional siempre es verdadera: (ch <= 0x0FF5A). decodew.cpp 525

Si lo hiciste, ¬°felicitaciones a ti!

El error radica en el hecho de que el operador de negación lógica (!) No se aplica a toda la condición, sino solo a su primera subexpresión:

 !((ch >= 0x0FF10) && (ch <= 0x0FF19)) 

Si esta condición es verdadera, el valor de la variable ch se encuentra en el rango [0x0FF10 ... 0x0FF19]. Por lo tanto, otras cuatro comparaciones ya no tienen sentido: siempre serán verdaderas o falsas.

Para evitar tales errores, vale la pena seguir algunas reglas. En primer lugar, es muy conveniente e informativo alinear el código como una tabla. En segundo lugar, no debe sobrecargar las expresiones con paréntesis. Por ejemplo, este código podría reescribirse así:

 const bool isLetterOrDigit = (ch >= 0x0FF10 && ch <= 0x0FF19) // 0..9 || (ch >= 0x0FF21 && ch <= 0x0FF3A) // A..Z || (ch >= 0x0FF41 && ch <= 0x0FF5A); // a..z if (!isLetterOrDigit) 

De esta manera, habrá menos paréntesis y, por otro lado, lo más probable es que notes un error ocasional.

Aquí viene la guinda del pastel: ¡pasemos al primer lugar!

Primer lugar


Fuente: Sistema conmocionado: errores interesantes en el código fuente del choque del sistema legendario

¡El mejor finalista de hoy es un error del legendario System Shock! Es un juego lanzado hace bastante tiempo en 1994, que se convirtió en un predecesor e inspiración para juegos tan icónicos, como Dead Space, BioShock y Deus Ex.

Pero primero tengo algo que confesar. Lo que voy a mostrar ahora no contiene ning√ļn error. En realidad, ni siquiera es un c√≥digo, ¬°pero no pude evitar compartirlo contigo!

La cuestión es que mientras analizaba el código fuente del juego, mi colega Victoria descubrió muchos comentarios fascinantes. En diferentes fragmentos encontró algunos comentarios ingeniosos e irónicos, e incluso poesía.

 // I'll give you fish, I'll give you candy, // I'll give you, everything I have in my hand // that kid from the wrong side came over my house again, // decapitated all my dolls // and if you bore me, you lose your soul to me // - "Gepetto", Belly, _Star_ // And here, ladies and gentlemen, // is a celebration of C and C++ and their untamed passion... // ================== TerrainData terrain_info; // Now the actual stuff... // ======================= // this is all outrageously horrible, as we dont know what // we really need to deal with here // And if you thought the hack for papers was bad, // wait until you see the one for datas... - X // Returns whether or not in the humble opinion of the // sound system, the sample should be politely obliterated // out of existence // it's a wonderful world, with a lot of strange men // who are standing around, and they all wearing towels 

As√≠ es como se ven los comentarios que los desarrolladores dejaron en los juegos en los √ļltimos a√Īos 90 ... Por cierto, Doug Church, un dise√Īador jefe de System Shock, tambi√©n estaba ocupado escribiendo c√≥digo. Qui√©n sabe, ¬Ņquiz√°s algunos de estos comentarios fueron escritos por √©l? Espero que las cosas de los hombres en toallas no sean su obra :)

Conclusión


En conclusión, me gustaría agradecer a mis colegas por buscar nuevos errores y escribir sobre ellos en artículos. Gracias chicos Sin ti, este artículo no sería tan interesante.

Tambi√©n me gustar√≠a contar un poco sobre nuestros logros, ya que durante todo el a√Īo no hemos estado ocupados solo buscando errores. Tambi√©n hemos estado desarrollando y mejorando el analizador, lo que result√≥ en cambios significativos.

Por ejemplo, hemos agregado soporte para varios compiladores nuevos y ampliado la lista de reglas de diagnóstico. También hemos implementado el soporte inicial de los estándares MISRA C y MISRA C ++ . La nueva característica más importante y lenta fue la compatibilidad con un nuevo idioma. ¡Sí, ahora podemos analizar el código en Java ! Y lo que es más, tenemos un ícono renovado :)

También quiero agradecer a nuestros lectores. ¡Gracias por leer nuestros artículos y escribirnos! ¡Eres muy receptivo y eres muy importante para nosotros!

Nuestros 10 errores principales de C ++ de 2018 han llegado a su fin. ¬ŅQu√© fragmentos te gustaron m√°s y por qu√©? ¬ŅEncontraste algunos ejemplos interesantes en 2018?

Todo lo mejor, ¡hasta la próxima!

Source: https://habr.com/ru/post/444568/


All Articles