Ugrás a tartalomhoz

off-by-one error

A Wikiszótárból, a nyitott szótárból
(OBOE szócikkből átirányítva)


Főnév

off-by-one error (tsz. off-by-one errors)

  1. (informatika) Az off-by-one error az egyik leggyakoribb programozási hiba, amely akkor fordul elő, amikor egy ciklus, tömb- vagy lista-kezelés, számlálás vagy bármilyen iteratív feldolgozás során egy elemmel kevesebbszer vagy többször fut le a kód, mint ahogyan azt a programozó tervezte.

Röviden: pontosan eggyel kevesebb vagy több lépést hajtunk végre, mint kéne.



🚦 Hol fordul elő leggyakrabban?

👉 Ciklusokban:

  • for
  • while
  • do-while

👉 Tömbök, listák bejárásakor (indexelés):

  • std::vector
  • std::array
  • nyers C-tömbök (int arr[])

👉 Számlálásokban:

  • pl. amikor 10 elemet akarunk kiírni, de csak 9-et írunk ki (vagy 11-et)

👉 String kezelésében:

  • karakterenkénti feldolgozás
  • ‘\0’ terminátor figyelmen kívül hagyása vagy túllépése



🤔 Miért történik meg?

1️⃣ Programozási hibák az indexelésben

Sok nyelvben, így C++-ban is, a tömbök 0-tól indexelnek:

  • Az első elem indexe: 0
  • Az utolsó elem indexe: size - 1

Nagyon gyakran félreértjük:

for (int i = 1; i <= 10; i++) // 1-től 10-ig — itt OK ha 1-től akarunk számozni

De:

for (int i = 0; i <= 10; i++) // Hoppá! Ha 0-tól 10-ig megyek egy 10 elemű tömbön, túlindexelem.

Ha egy 10 elemű tömböt járok be:

  • Helyes: i < 10 (index 0..9)
  • Hibás: i <= 10off-by-one error

2️⃣ Feltételhibák a ciklusban

Nagyon gyakran a feltételben rossz operátort választunk:

for (int i = 0; i <= N; i++) // gyakori hiba

Ha N egy tömb mérete, a helyes:

for (int i = 0; i < N; i++)

3️⃣ Ciklus végfeltétele rossz megfogalmazása

Sok kezdő programozó bizonytalan abban, hogy meddig kell futni:

  • Kezdő index: 0 vagy 1?
  • Befejezés: <, <=, > vagy >=?



📚 Klasszikus példák

1️⃣ String bejárása hibásan

char str[] = "Hello";
for (int i = 0; i <= strlen(str); i++) { // HIBA
    std::cout << str[i];
}

Helyes:

for (int i = 0; i < strlen(str); i++) {
    std::cout << str[i];
}

Miért? strlen(str) nem tartalmazza a \0 terminátort. Ha <=-t használunk, akkor véletlenül kiírjuk a \0-t is, ami hibát okozhat.



2️⃣ Tömb bejárása hibásan

int arr[5] = {1, 2, 3, 4, 5};
for (int i = 0; i <= 5; i++) { // HIBA: i == 5 túlindexelés!
    std::cout << arr[i] << " ";
}

Helyes:

for (int i = 0; i < 5; i++) {
    std::cout << arr[i] << " ";
}

3️⃣ Ciklus vége hibás

Ha 1-től N-ig akarok számolni:

for (int i = 1; i < N; i++) // Csak 1..N-1-ig fut → HIBA

Helyes:

for (int i = 1; i <= N; i++) // 1..N → helyes

🔍 Miért veszélyes?

✅ A program lefut — nem fordítási hiba. ✅ A hibát sokszor nehéz észrevenni → csak rossz eredményt kapunk. ✅ Néha memóriaterületet túllépünk → undefined behavior, akár crash.

Klasszikus eset:

int arr[5];
arr[5] = 42; // nincs 5. index — memóriarombolás!

💡 Tipikus hibaforrások

Helyzet Mi a tipikus off-by-one ok?
Tömb bejárása < vagy <= összekeverése
String feldolgozása strlen / size / terminátor figyelmen kívül hagyása
Számlálás Rosszul definiált kezdő- vagy végérték
Range ciklus i <= N vs. i < N hibás használata
Nested loop Belső és külső ciklus indexe elcsúszik



🛠️ Hogyan kerüld el?

1️⃣ Szokj rá a for (int i = 0; i < N; i++) mintára

  • Ha tömböt, listát jársz be → mindig i < N
  • Ne használj <= ha indexelsz!

2️⃣ Ha tudod, használd range-based for loop-ot:

for (auto val : vec) {
    std::cout << val << " ";
}

→ Nincs index, nincs off-by-one hiba.

3️⃣ Használj konstansot:

const int SIZE = 10;
for (int i = 0; i < SIZE; i++) { ... }

→ így biztosan nem tévesztesz el méretet.

4️⃣ Teszteld a következő eseteket:

  • Üres tömb (size == 0)
  • 1 elemű tömb (size == 1)
  • N elemű tömb

Ha ezekben is működik → helyes ciklusod van.

5️⃣ Ellenőrző kérdés:

Hány iterációt vársz? → Ha nem tudod pontosan megmondani, gondold át újra a ciklust!



⚠️ Specialitások, amiknél különösen gyakori

  • Multidimenziós tömbök
  • Substring feldolgozás
  • Fájl sorainak számlálása
  • Rendezés, keresés (pl. binary search)
  • Numerikus algoritmusok (pl. sum, product)



👨‍💻 Példa egy elcsúszott algoritmusra

Hibás factorial:

int factorial(int n) {
    int result = 1;
    for (int i = 1; i < n; i++) { // Hibás! Nem szorzom be az n-t!
        result *= i;
    }
    return result;
}

Helyes:

int factorial(int n) {
    int result = 1;
    for (int i = 1; i <= n; i++) { // Helyes: 1..n
        result *= i;
    }
    return result;
}

🎁 Összefoglalás

Off-by-one error = eggyel kevesebb vagy több iteráció ✅ Gyakori oka: hibás feltétel, rossz határ (<= vs <) ✅ Nehéz észrevenni, mert nem fordítási hiba ✅ Sok adatstruktúra 0-tól indexel, ezért különösen figyelni kell ✅ Legegyszerűbb elkerülni:

  • range-based for
  • helyes sablon (for (int i = 0; i < N; i++))
  • tudatos határok definiálása
  • tesztelés 0, 1, 2 elemre



Végső tanács

Az OBOE az egyik legelső és leggyakoribb “apró, de veszélyes” programozási hiba, amit szinte minden kezdő és haladó programozó elkövet. Ha mindig tisztában vagy azzal, hogy mit akarsz bejárni (indexeket vagy értékeket), akkor ezt a hibát könnyen elkerülheted.