CSES - Bugin löytäminen

Hyödyllisiä käännöslippuja

-Wall

Asettaa monia hyödyllisiä varoituksia päälle. Bugi saattaa löytyä miettimällä tarkkaan, mitä varoitus tarkoittaa. Tätä kannattaa käyttää aina.

-fsanitize=address

Tarkistaa kaikki muistinkäytöt, ja lopettaa koodin suorituksen heti, kun muistia käytetään virheellisesti. Tämä on hyödyllinen, koska monet muistivirheet, kuten pienet taulukonylitykset, eivät aiheuta segfaultia suoraan, vaan virhe ilmenee usein vasta myöhemmin arvaamattomalla tavalla.

-D_GLIBCXX_DEBUG

Standardikirjaston käsinkirjoitettu debug-toiminnallisuus. Kuten -fsanitize=address, havaitsee muistivirheitä ja iteraattorien väärinkäyttöä. Se voi myös huomata hienovaraisempia bugeja, kuin pelkkiä muistivirheitä, ja antaa hyödyllisemmän kuvauksen virheestä.

Assert

assert(ehto);

assert-funktio kaataa ohjelman heti, jos annettu ehto ei päde. Hyvin hyödyllinen bugin juurisyyn etsimisessä, eli "missä kohtaa menee pieleen".

Brutetestaus

Brutetesterin voi kirjoittaa monella tavalla, mutta ainakin jokin tapa on hyvä osata rutiinilla.

Alla esimerkki C++-brutetestaajan rungosta. Varsinainen koodi on tiedostossa koodi.cpp käännettynä tiedostoon koodi, bruteratkaisu on tiedostossa brute.cpp, käännettynä brute.

#include <bits/stdc++.h>
using namespace std;

int main() {
  while (true) {
    // Generoi sattumanvarainen syöte tiedostoon
    ofstream f("syote");
    int n = rand()%10+1;
    f << n << endl;
    f.close();

    // Aja molemmat koodit
    system("./brute < syote > out_brute");
    system("./koodi < syote > out_koodi");

    // Vertaa tulosteita
    if (system("diff -b out_brute out_koodi")) {
      cout << "Virhe löytyi!" << endl;
      break;
    }
  }
}

Komento diff lipulla -b vertailee kätevästi tulosteita välittämättä ylimääräisistä välilyönneistä, kuten kisajärjestelmätkin.

HUOM: Aiemmin tässä mainittu -w-lippu ei toimi ihan halutusti, vaan se ei välitä välilyönneistä ollenkaan. -b harmillisesti erottaa rivinvaihdot ja välilyönnit.

Välillä ei ole tarkoituskaan, että tulosteet ovat identtisiä, vaan oikeellisuus pitää tarkastaa jollain toisella tavalla. Tällöin tulosteet voidaan lukea käyttäen ifstream-virtoja:

    // Vertaa tulosteiden ensimmäistä lukua
    ifstream fa("out_brute"), fb("out_koodi");
    long a, b;
    fa >> a;
    fb >> b;
    if (a != b) {
      cout << "Virhe löytyi!" << endl;
      break;
    }

Jos ja kun brutetesteri löytää virheen, pysähtymisen jälkeen tiedostot syote, out_brute, out_koodi vastaavat sitä syötettä, jolla virhe löytyi. Koodia voi siis ajaa samalla syötteellä uudestaan. Voi olla hyödyllistä ottaa syöte talteen, ettei vahingossa ylikirjoita sitä myöhemmin.

cp syote syote_rikki
./koodi < syote_rikki

Syötettä voi myös käyttää GDB:ssä (muista oikeat käännösliput, ks. GDB-ohje):

gdb koodi
run < syote_rikki

Toinen hyvä kieli brutetesterin toteuttamisielle on Python, tai miksei Bash-skriptikin, jos syötteen generointi tapahtuu toisessa ohjelmassa.

Errichton debuggausvideo