Numerosos errores tipográficos y código Copy-Paste se convirtieron en el tema de un artículo adicional sobre la verificación del código Haiku por el analizador PVS-Studio. Sin embargo, habrá errores asociados no tanto con errores tipográficos, sino con descuido y refactorización fallida. Los ejemplos de errores encontrados demuestran cuán fuerte es el factor humano en el desarrollo de software.
Introduccion
Haiku es un sistema operativo gratuito y de código abierto para computadoras personales. Actualmente, un grupo internacional de desarrolladores está trabajando activamente en los componentes del sistema. De los últimos desarrollos significativos en el desarrollo fue portar LibreOffice al sistema operativo y el lanzamiento de la primera versión beta de R1 Beta 1.
El equipo de desarrolladores del analizador de código estático
PVS-Studio supervisa el desarrollo del proyecto desde 2015 y publica revisiones de defectos de código. Esta es la cuarta revisión de todos los tiempos. Puede familiarizarse con artículos anteriores en estos enlaces:
- Comprobación del sistema operativo Haiku (familia BeOS). Parte 1
- Comprobación del sistema operativo Haiku (familia BeOS). Parte 2 ;
- Cómo dispararte en el pie en C y C ++. Colección de Recetas Haiku OS
Una característica del último análisis de código es la capacidad de usar la versión oficial de PVS-Studio para Linux. En 2015, no estaba allí, así como un informe conveniente para ver los errores. Ahora enviaremos a los desarrolladores un informe completo en un formato conveniente.
Clásicos del género
V501 Hay
subexpresiones idénticas a la izquierda y a la derecha del operador '-': (addr_t) b - (addr_t) b BitmapManager.cpp 51
int compare_app_pointer(const ServerApp* a, const ServerApp* b) { return (addr_t)b - (addr_t)b; }
Cada programador en su vida debe mezclar las variables
a y
b ,
x e
y ,
i y
j ... etc.
V501 Hay
subexpresiones idénticas a la izquierda y a la derecha de '||' operador: input == __null || input == __null MediaClient.cpp 182
status_t BMediaClient::Unbind(BMediaInput* input, BMediaOutput* output) { CALLED(); if (input == NULL || input == NULL) return B_ERROR; if (input->fOwner != this || output->fOwner != this) return B_ERROR; input->fBind = NULL; output->fBind = NULL; return B_OK; }
En esta condición, el mismo puntero de
entrada se verifica dos veces, y el puntero de
salida permanece sin marcar, lo que puede conducir a la desreferenciación del puntero nulo.
Código corregido:
if (input == NULL || output == NULL) return B_ERROR;
V583 El
operador '?:', Independientemente de su expresión condicional, siempre devuelve el mismo valor: 500000. usb_modeswitch.cpp 361
static status_t my_transfer_data(....) { .... do { bigtime_t timeout = directionIn ? 500000 : 500000; result = acquire_sem_etc(device->notify, 1, B_RELATIVE_TIMEOUT, timeout); .... } while (result == B_INTERRUPTED); .... }
El operador ternario perdió su significado cuando el programador cometió un error y escribió dos valores de retorno idénticos:
500000 .
V519 A la variable 'm_kindex1' se le asignan valores dos veces seguidas. Quizás esto sea un error. Verifique las líneas: 40, 41. agg_trans_double_path.cpp 41
trans_double_path::trans_double_path() : m_kindex1(0.0), m_kindex2(0.0), m_base_length(0.0), m_base_height(1.0), m_status1(initial), m_status2(initial), m_preserve_x_scale(true) { } void trans_double_path::reset() { m_src_vertices1.remove_all(); m_src_vertices2.remove_all(); m_kindex1 = 0.0; m_kindex1 = 0.0; m_status1 = initial; m_status2 = initial; }
Se cometió un error en la función de
reinicio : un error tipográfico en el índice de
m_kindex2 . El valor de esta variable no se restablecerá, lo que probablemente afectará la ejecución de otros fragmentos de código.
V501 Hay
subexpresiones idénticas a la izquierda y a la derecha del operador '>': fg [order_type :: R]> fg [order_type :: R] agg_span_image_filter_rgba.h 898
typedef Source source_type; typedef typename source_type::color_type color_type; typedef typename source_type::order_type order_type; void generate(color_type* span, int x, int y, unsigned len) { .... if(fg[0] < 0) fg[0] = 0; if(fg[1] < 0) fg[1] = 0; if(fg[2] < 0) fg[2] = 0; if(fg[3] < 0) fg[3] = 0; if(fg[order_type::A] > base_mask) fg[order_type::A] = base_mask; if(fg[order_type::R] > fg[order_type::R])fg[order_type::R] = fg[order_type::R]; if(fg[order_type::G] > fg[order_type::G])fg[order_type::G] = fg[order_type::G]; if(fg[order_type::B] > fg[order_type::B])fg[order_type::B] = fg[order_type::B]; .... }
En las últimas líneas hay una comparación inmediata de las mismas variables y la asignación de las mismas variables. No puedo asumir lo que se pensó aquí. Simplemente marque el fragmento como sospechoso.
V570 La variable 'wPipeIndex' se asigna a sí misma. CEchoGals_transport.cpp 244
ECHOSTATUS CEchoGals::CloseAudio (....) { .... wPipeIndex = wPipeIndex; m_ProcessId[ wPipeIndex ] = NULL; m_Pipes[ wPipeIndex ].wInterleave = 0; .... }
La variable
wPipeIndex se inicializa a su propio valor. Lo más probable es que se haya cometido un error tipográfico.
Errores con punteros
V522 Puede tener lugar la desreferenciación del puntero nulo 'currentInterface'. Device.cpp 258
Device::Device(....) : .... { .... usb_interface_info* currentInterface = NULL;
El puntero
currentInterface se inicializa inicialmente a cero y luego se verifica al ingresar la rama de
instrucción de cambio , pero no en todos los casos. El analizador advierte que al pasar a la etiqueta
USB_DESCRIPTOR_ENDPOINT_COMPANION ,
es posible desreferenciar un puntero nulo.
V522 Puede tener lugar la desreferenciación del puntero nulo 'directorio'. PathMonitor.cpp 1465
bool PathHandler::_EntryCreated(....) { .... Directory* directory = directoryNode->ToDirectory(); if (directory == NULL) {
Creo que hubo un error al comparar el puntero del
directorio con un valor nulo y la condición debería ser la opuesta. En la implementación actual, si la variable
dryRun es
falsa , el puntero nulo del
directorio será desreferenciado.
V522 Puede tener lugar la desreferenciación del puntero nulo 'input'. MediaRecorder.cpp 343
void GetInput(media_input* input); const media_input& BMediaRecorder::MediaInput() const { CALLED(); media_input* input = NULL; fNode->GetInput(input); return *input; }
El puntero de
entrada se inicializa a cero y permanece en ese valor, porque en la función GetInput, el puntero no cambia. Otros métodos de la clase
BMediaRecorder escriben de manera diferente, por ejemplo:
status_t BMediaRecorder::_Connect(....) { ....
Aquí todo es correcto, pero es imposible escribir así en el primer fragmento de código; de lo contrario, la función devolverá un enlace al objeto local, pero de alguna manera el código debe repararse.
V522 Puede tener lugar la desreferenciación del puntero nulo 'mustFree'. RequestUnflattener.cpp 35
status_t Reader::Read(int32 size, void** buffer, bool* mustFree) { if (size < 0 || !buffer || mustFree)
En la expresión condicional, donde se verifican todos los datos de entrada no válidos, se realizó un error tipográfico al verificar el puntero
mustFree . Lo más probable es que la salida de la función esté en el valor cero de este puntero:
if (size < 0 || !buffer || !mustFree)
V757 Es posible que una variable incorrecta se compare con nullptr después de la conversión de tipo usando 'dynamic_cast'. Líneas de verificación: 474, 476. recover.cpp 474
void checkStructure(Disk &disk) { .... Inode* missing = gMissing.Get(run); dir = dynamic_cast<Directory *>(missing); if (missing == NULL) { .... } .... }
En lugar del puntero
faltante , debe verificar el puntero
dir después de la conversión de tipo. Por cierto, los programadores de C # a
menudo cometen un error similar. Esto prueba una vez más que algunos errores son independientes del lenguaje utilizado.
Un par de lugares más similares en el código:
- V757 Es posible que una variable incorrecta se compare con nullptr después de la conversión de tipo usando 'dynamic_cast'. Líneas de verificación: 355, 357. ExpandoMenuBar.cpp 355
- V757 Es posible que una variable incorrecta se compare con nullptr después de la conversión de tipo usando 'dynamic_cast'. Líneas de verificación: 600, 601. ValControl.cpp 600
Errores de índice
V557 Array overrun es posible. El índice 'BT_SCO' apunta más allá de la matriz. h2upper.cpp 75
struct bt_usb_dev { .... struct list nbuffersTx[(1 + 1 + 0 + 0)];
La
matriz bdev-> nbuffersTx consta de solo dos elementos, mientras que en el código se accede por la constante BT_SCO, que tiene un valor de 3. Se produce una salida garantizada más allá de los límites de la matriz.
V557 Array overrun es posible. La función 'ieee80211_send_setup' procesa el valor '16'. Inspeccione el cuarto argumento. Verifique las líneas: 842, 911. ieee80211_output.c 842
struct ieee80211_node { .... struct ieee80211_tx_ampdu ni_tx_ampdu[16];
Otra forma de salir de la matriz. En este caso, solo un artículo. El análisis interprocedimiento ayudó a identificar una situación en la que se
accedió a la matriz
ni-> ni_tx_ampdu , que consta de 16 elementos, en el índice
16 . En C y C ++, la indexación de matriz se realiza desde cero.
V781 El valor de la variable 'vector' se verifica después de su uso. Quizás haya un error en la lógica del programa. Líneas de verificación: 802, 805. oce_if.c 802
#define OCE_MAX_EQ 32 typedef struct oce_softc { .... OCE_INTR_INFO intrs[OCE_MAX_EQ]; .... } OCE_SOFTC, *POCE_SOFTC; static int oce_alloc_intr(POCE_SOFTC sc, int vector, void (*isr) (void *arg, int pending)) { POCE_INTR_INFO ii = &sc->intrs[vector]; int rc = 0, rr; if (vector >= OCE_MAX_EQ) return (EINVAL); .... }
El analizador detectó una llamada a la
matriz sc-> intrs en un índice no válido (que va más allá de los límites de la matriz). La razón de esto es el orden de código incorrecto. Aquí, primero se accede a la matriz y luego se realiza una comprobación para ver si el valor del índice es válido.
Alguien puede decir que no habrá problemas. Aquí, después de todo, el valor del elemento de matriz no se recupera, sino que simplemente se toma la dirección de la celda. No, no puedes hacerlo de todos modos. Leer más: "
Desreferenciar un puntero nulo conduce a un comportamiento indefinido ".
V519 A la variable se le asignan valores dos veces seguidas. Quizás esto sea un error. Líneas de verificación: 199, 200. nvme_ctrlr.c 200
static void nvme_ctrlr_set_intel_supported_features(struct nvme_ctrlr *ctrlr) { bool *supported_feature = ctrlr->feature_supported; supported_feature[NVME_INTEL_FEAT_MAX_LBA] = true; supported_feature[NVME_INTEL_FEAT_MAX_LBA] = true; supported_feature[NVME_INTEL_FEAT_NATIVE_MAX_LBA] = true; supported_feature[NVME_INTEL_FEAT_POWER_GOVERNOR_SETTING] = true; supported_feature[NVME_INTEL_FEAT_SMBUS_ADDRESS] = true; supported_feature[NVME_INTEL_FEAT_LED_PATTERN] = true; supported_feature[NVME_INTEL_FEAT_RESET_TIMED_WORKLOAD_COUNTERS] = true; supported_feature[NVME_INTEL_FEAT_LATENCY_TRACKING] = true; }
Al
elemento de matriz con el índice
NVME_INTEL_FEAT_MAX_LBA se le asigna el mismo valor. Afortunadamente, todas las constantes posibles están representadas en esta función, y el código es simplemente el resultado de la programación Copy-Paste. Pero la probabilidad de cometer errores de esta manera es muy alta.
V519 La
variable 'copiedPath [len]' tiene valores asignados dos veces sucesivamente. Quizás esto sea un error. Líneas de verificación: 92, 93. kernel_emu.cpp 93
int UserlandFS::KernelEmu::new_path(const char *path, char **copy) { ....
Y aquí el desarrollador no tuvo suerte con la copia. El símbolo "punto" se agrega a una determinada línea y se sobrescribe inmediatamente con un terminal cero. Muy probablemente, el autor del código copió la cadena y olvidó incrementar el índice.
Condiciones extrañas
V517 El uso del
patrón 'if (A) {...} else if (A) {...}' fue detectado. Hay una probabilidad de presencia de error lógico. Líneas de verificación: 1407, 1410. FindPanel.cpp 1407
void FindPanel::BuildAttrQuery(BQuery* query, bool &dynamicDate) const { .... case B_BOOL_TYPE: { uint32 value; if (strcasecmp(textControl->Text(), "true") == 0) { value = 1; } else if (strcasecmp(textControl->Text(), "true") == 0) { value = 1; } else value = (uint32)atoi(textControl->Text()); value %= 2; query->PushUInt32(value); break; } .... }
Copiar el código inmediatamente condujo a dos errores. Las expresiones condicionales son idénticas. Lo más probable es que uno de ellos tenga una comparación con la cadena "falso", en lugar de "verdadero". A continuación, en la rama que procesa el valor "falso", debe cambiar el valor del
valor de
1 a
0 . El algoritmo supone que cualquier otro valor que no sea
verdadero o
falso se convertirá en un número utilizando la función
atoi , pero debido a un error, el texto también obtendrá el texto "falso".
V547 La expresión 'error == ((int) 0)' siempre es verdadera. Directory.cpp 688
int32 BDirectory::CountEntries() { status_t error = Rewind(); if (error != B_OK) return error; int32 count = 0; BPrivate::Storage::LongDirEntry entry; while (error == B_OK) { if (GetNextDirents(&entry, sizeof(entry), 1) != 1) break; if (strcmp(entry.d_name, ".") != 0 && strcmp(entry.d_name, "..") != 0) count++; } Rewind(); return (error == B_OK ? count : error); }
El analizador detectó que el valor de la variable de
error siempre será
B_OK . Lo más probable es que se haya pasado por alto una modificación de esta variable en el
ciclo while .
V564 El operador '&' se aplica al valor de tipo bool. Probablemente haya olvidado incluir paréntesis o tenga la intención de utilizar el operador '&&'. strtod.c 545
static int lo0bits(ULong *y) { int k; ULong x = *y; .... if (!(x & 1)) { k++; x >>= 1; if (!x & 1)
Lo más probable es que, en la última expresión condicional, olvidaron colocar corchetes, como en las condiciones anteriores. Probablemente, allí también el operador de negación debe estar fuera de los corchetes:
if (!(x & 1))
V590 Considere inspeccionar esta expresión. La expresión es excesiva o contiene un error de imprenta. PoseView.cpp 5851
bool BPoseView::AttributeChanged(const BMessage* message) { .... result = poseModel->OpenNode(); if (result == B_OK || result != B_BUSY) break; .... }
Esto no es obvio, pero el resultado de la condición no depende del valor de B_OK. Por lo tanto, se puede simplificar:
If (result != B_BUSY) break;
Esto se puede verificar fácilmente construyendo una tabla de verdad para los valores de la variable de
resultado . Si necesita considerar específicamente otros valores distintos de
B_OK y
B_BUSY , entonces el código debe reescribirse de manera diferente.
Dos condiciones más similares:
- V590 Considere inspeccionar esta expresión. La expresión es excesiva o contiene un error de imprenta. Tracker.cpp 1714
- V590 Considere inspeccionar esta expresión. La expresión es excesiva o contiene un error de imprenta. if_ipw.c 1871
V590 Considere inspeccionar el 'argc == 0 || argc! = expresión de 2 '. La expresión es excesiva o contiene un error de imprenta. cmds.c 2667
void unsetoption(int argc, char *argv[]) { .... if (argc == 0 || argc != 2) { fprintf(ttyout, "usage: %s option\n", argv[0]); return; } .... }
Quizás este es el ejemplo más simple que demuestra el funcionamiento del diagnóstico
V590 . Es necesario mostrar la descripción del programa si no se pasan argumentos o si no hay dos. Obviamente, cualquier valor que no sea dos, incluido cero, no satisfará la condición. Por lo tanto, la condición se puede simplificar fácilmente a lo siguiente:
if (argc != 2) { fprintf(ttyout, "usage: %s option\n", argv[0]); return; }
V590 Considere inspeccionar el '* ptr =='; ' && * ptr! = '\ 0' 'expresión. La expresión es excesiva o contiene un error de imprenta. pc.c 316
ULONG parse_expression(char *str) { .... ptr = skipwhite(ptr); while (*ptr == SEMI_COLON && *ptr != '\0') { ptr++; if (*ptr == '\0') continue; val = assignment_expr(&ptr); } .... }
En este ejemplo, el operador lógico ha cambiado, pero la lógica se ha mantenido igual. Aquí, la condición del ciclo while depende solo de si el carácter es igual a
SEMI_COLON o no.
V590 Considere inspeccionar esta expresión. La expresión es excesiva o contiene un error de imprenta. writembr.cpp 99
int main(int argc, char** argv) { .... string choice; getline(cin, choice, '\n'); if (choice == "no" || choice == "" || choice != "yes") { cerr << "MBR was NOT written" << endl; fs.close(); return B_ERROR; } .... }
Ya hay tres condiciones en este ejemplo. También se puede simplificar para verificar si el usuario ha seleccionado "sí" o no:
if (choice != "yes") { cerr << "MBR was NOT written" << endl; fs.close(); return B_ERROR; }
Errores varios
V530 Se requiere utilizar el valor de retorno de la función 'comenzar'. IMAPFolder.cpp 414
void IMAPFolder::RegisterPendingBodies(...., const BMessenger* replyTo) { .... IMAP::MessageUIDList::const_iterator iterator = uids.begin(); for (; iterator != uids.end(); iterator++) { if (replyTo != NULL) fPendingBodies[*iterator].push_back(*replyTo); else fPendingBodies[*iterator].begin();
El analizador detectó una llamada sin sentido al iterador
begin (). No puedo adivinar cómo arreglar el código. El desarrollador debe prestar atención a este lugar.
V609 Divide por cero. Rango del denominador [0..64]. UiUtils.cpp 544
static int32 GetSIMDFormatByteSize(uint32 format) { switch (format) { case SIMD_RENDER_FORMAT_INT8: return sizeof(char); case SIMD_RENDER_FORMAT_INT16: return sizeof(int16); case SIMD_RENDER_FORMAT_INT32: return sizeof(int32); case SIMD_RENDER_FORMAT_INT64: return sizeof(int64); case SIMD_RENDER_FORMAT_FLOAT: return sizeof(float); case SIMD_RENDER_FORMAT_DOUBLE: return sizeof(double); } return 0; } const BString& UiUtils::FormatSIMDValue(const BVariant& value, uint32 bitSize, uint32 format, BString& _output) { _output.SetTo("{"); char* data = (char*)value.ToPointer(); uint32 count = bitSize / (GetSIMDFormatByteSize(format) * 8);
La función
GetSIMDFormatByteSize en realidad devuelve
0 como valor predeterminado, lo que podría conducir a la división por cero.
V654 La condición '
SpecificSequence ! = Sequence' del bucle siempre es falsa. pthread_key.cpp 55
static void* get_key_value(pthread_thread* thread, uint32 key, int32 sequence) { pthread_key_data& keyData = thread->specific[key]; int32 specificSequence; void* value; do { specificSequence = keyData.sequence; if (specificSequence != sequence) return NULL; value = keyData.value; } while (specificSequence != sequence); keyData.value = NULL; return value; }
El analizador tiene razón en que la condición de la declaración
while siempre es falsa. Debido a esto, no se realiza más de una iteración en un bucle. En otras palabras, nada cambiaría si escribes
while (0) . Todo esto es muy extraño y este código contiene algún tipo de error en la lógica del trabajo. Los desarrolladores deben prestar atención a este lugar.
V672 Probablemente no sea necesario crear la nueva variable 'ruta' aquí. Uno de los argumentos de la función posee el mismo nombre y este argumento es una referencia. Líneas de verificación: 348, 429. translate.cpp 429
status_t Translator::FindPath(...., TypeList &path, double &pathQuality) { .... TypeList path; double quality; if (FindPath(&formats[j], stream, typesSeen, path, quality) == B_OK) { if (bestQuality < quality * formatQuality) { bestQuality = quality * formatQuality; bestPath.SetTo(path); bestPath.Add(formats[j].type); status = B_OK; } } .... }
La variable de
ruta se pasa a la función
FindPath por referencia. Esto significa que es posible modificar esta variable en el cuerpo de la función. Pero aquí hay una variable local del mismo nombre que se está modificando. En este caso, todos los cambios permanecerán solo en la variable local. Quizás debería cambiar el nombre o eliminar la variable local.
V705 Es posible que el bloque 'else' haya sido olvidado o comentado, alterando así las lógicas de operación del programa. HostnameView.cpp 109
status_t HostnameView::_LoadHostname() { BString fHostnameString; char hostname[MAXHOSTNAMELEN]; if (gethostname(hostname, MAXHOSTNAMELEN) == 0) { fHostnameString.SetTo(hostname, MAXHOSTNAMELEN); fHostname->SetText(fHostnameString); return B_OK; } else return B_ERROR; }
Un ejemplo de diseño de código pobre. La palabra clave
else "congelada" todavía no cambia la lógica, pero esto es hasta que se inserta el primer fragmento de código antes de la
declaración de devolución .
El 'menú' del parámetro
V763 siempre se reescribe en el cuerpo de la función antes de usarse. video.cpp 648
bool video_mode_hook(Menu *menu, MenuItem *item) { video_mode *mode = NULL; menu = item->Submenu(); item = menu->FindMarked(); .... }
Hubo muchos casos en los que los argumentos de la función se sobrescriben inmediatamente en la entrada de la función. Este comportamiento es engañoso para otros desarrolladores que invocan estas mismas funciones.
La lista completa de lugares sospechosos:
- V763 El parámetro 'force_16bit' siempre se reescribe en el cuerpo de la función antes de usarse. ata_adapter.cpp 151
- V763 El parámetro 'force_16bit' siempre se reescribe en el cuerpo de la función antes de usarse. ata_adapter.cpp 179
- El 'menú' del parámetro V763 siempre se reescribe en el cuerpo de la función antes de usarse. video.cpp 264
- V763 El parámetro 'length' siempre se reescribe en el cuerpo de la función antes de usarse. MailMessage.cpp 677
- V763 El parámetro 'entrada' siempre se reescribe en el cuerpo de la función antes de ser utilizado. IconCache.cpp 773
- V763 El parámetro 'entrada' siempre se reescribe en el cuerpo de la función antes de ser utilizado. IconCache.cpp 832
- V763 El parámetro 'entrada' siempre se reescribe en el cuerpo de la función antes de ser utilizado. IconCache.cpp 864
- V763 El parámetro 'rect' siempre se reescribe en el cuerpo de la función antes de usarse. ErrorLogWindow.cpp 56
- El parámetro V763 'updateRect' siempre se reescribe en el cuerpo de la función antes de usarse. CalendarMenuWindow.cpp 49
- V763 El parámetro 'rect' siempre se reescribe en el cuerpo de la función antes de usarse. MemoryView.cpp 165
- V763 El parámetro 'rect' siempre se reescribe en el cuerpo de la función antes de usarse. TypeEditors.cpp 1124
- V763 El parámetro 'altura' siempre se reescribe en el cuerpo de la función antes de usarse. Workspaces.cpp 857
- V763 El parámetro 'ancho' siempre se reescribe en el cuerpo de la función antes de usarse. Workspaces.cpp 856
- El parámetro 'frame' V763 siempre se reescribe en el cuerpo de la función antes de ser utilizado. SwatchGroup.cpp 48
- El parámetro 'frame' V763 siempre se reescribe en el cuerpo de la función antes de ser utilizado. PlaylistWindow.cpp 89
- V763 El parámetro 'rect' siempre se reescribe en el cuerpo de la función antes de usarse. ConfigView.cpp 78
- V763 El parámetro 'm' siempre se reescribe en el cuerpo de la función antes de usarse. mkntfs.c 3917
- El parámetro V763 'rxchainmask' siempre se reescribe en el cuerpo de la función antes de usarse. ar5416_cal.c 463
- V763 El parámetro 'c' siempre se reescribe en el cuerpo de la función antes de usarse. if_iwn.c 6854
Conclusión
El proyecto Haiku es una fuente de errores interesantes y raros. Reabastecimos nuestra base de datos de ejemplos de errores y solucionamos varios problemas en el analizador identificado durante el análisis de código.
Si no ha verificado su código con ninguna herramienta de análisis de código durante mucho tiempo, entonces algo de lo descrito probablemente también esté en su código. Use PVS-Studio en su proyecto para controlar la calidad del código si está escrito en C, C ++, C # o Java. Puede descargar el analizador sin registro y SMS
aquí .
¿Quieres probar Haiku y tienes alguna pregunta? Los desarrolladores de Haiku te invitan al
canal de telegramas .

Si desea compartir este artículo con una audiencia de habla inglesa, utilice el enlace a la traducción: Svyatoslav Razmyslov.
Los mejores algoritmos de copiar y pegar para C y C ++. Haiku OS Cookbook