as-if rule (tsz. as-if rules)
Röviden:
A fordító bármit megtehet a kóddal **– akár teljesen át is szervezheti –, feltéve, hogy a program viselkedése úgy tűnik, mintha a kód az eredeti sorrendben és módon futna le.
Ezt nevezzük “as-if” rule-nak, mert a programnak “úgy kell viselkednie, mintha” a kód az írt sorrendben, explicit utasítások szerint futott volna.
A C++ szabvány így fogalmaz (fordítva):
Egy C++ implementáció bármilyen optimalizációt végezhet, feltéve, hogy a program külső viselkedése megegyezik azzal, amit az explicit utasítások alapján várnánk el.
A „külső viselkedés” alatt például értjük:
std::cout
, fájlírás)std::cin
)
Példa:
int a = 2;
int b = a + 3;
return 0;
A b
kiszámítása sehol nem használódik fel – a fordító törölheti a teljes kódrészt.
int a = 5;
int b = 10;
int c = a + b;
A fordító kiszámíthatja c
értékét compile time-ban, és kihagyhatja a
és b
hozzárendelését, ha a
és b
nem használtak máshol. Ez nem sérti az as-if szabályt, mert a megfigyelhető hatás ugyanaz marad.
A fordító nem változtathatja meg a program megfigyelhető viselkedését.
Példa, ami nem megengedett:
std::cout << "Első\n";
std::cout << "Második\n";
A fordító nem cserélheti fel a kiírások sorrendjét, mert a kimenet más lesz, és ez megsértené az as-if szabályt.
A program futása során minden olyan művelet, amely hatással van a külvilágra, megfigyelhető:
Hatás | Megfigyelhető? |
---|---|
std::cout kimenet
|
✅ igen |
Fájlírás | ✅ igen |
Külső eszközhívás | ✅ igen |
Memóriafoglalás | ❌ nem |
Változó értékének módosítása (belső) | ❌ nem (ha nem kerül ki) |
Ez azt jelenti, hogy a fordító eltávolíthat vagy átrendezhet belső műveleteket, amíg ezek nem látszanak kívülről.
int x = 2 + 3;
A fordító egyszerűen:
int x = 5;
void f() {
int a = 5;
return;
int b = a * 10; // unreachable
}
A fordító eldobja a b
-re és a szorzásra vonatkozó utasítást.
int square(int x) { return x * x; }
int main() {
int y = square(10);
}
A fordító átalakíthatja így:
int y = 10 * 10;
Az as-if szabály figyelembe veszi a side effecteket (mellékhatásokat). Például:
int f() {
std::cout << "Side effect\n";
return 5;
}
int x = f();
A fordító nem hagyhatja el a f()
hívását, akkor sem, ha x
később nincs használva, mert a függvénynek van mellékhatása (kiír).
Bizonyos kapcsolókkal a fordító megtörheti az as-if szabályt tudatos engedéllyel.
Például -ffast-math
(GCC/Clang) lehetővé teszi lebegőpontos műveletek átrendezését, ami változtathat a kimeneten.
as-if
szabály nem sértheti a kód sorrendiségét, ha az megfigyelhető (pl. függvényhívások, értékadások, kivételek).
Tévhit | Valóság |
---|---|
„A fordító mindig a kódom sorrendjében hajtja végre az utasításokat.” | ❌ Nem. Csak úgy kell viselkednie, mintha így tenné. |
„Ha egy változót definiálok, az mindig lefut.” | ❌ Ha nincs hatása, a fordító eldobhatja. |
„A printf hívásokat sose hagyja el a fordító.”
|
✅ Mert azok megfigyelhető hatással bírnak. |
Jellemző | Leírás |
---|---|
Név | As-if rule („mintha szabály”) |
Funkciója | Optimalizáció engedélyezése, ha a viselkedés változatlan |
Alkalmazásának feltétele | A megfigyelhető hatások nem változhatnak |
Fordítói optimalizációk | Teljesen legálisak as-if szerint, ha nincs külső különbség |
Példa | Ki nem használt változók törlése, inline, értékek összevonása |
Kapcsolódik | Side-effect, sequencing, undefined behavior |
Az as-if szabály a C++ (és a C) nyelvek optimalizációs rugalmasságának alapja. Lehetővé teszi, hogy a fordító agresszíven optimalizáljon, anélkül, hogy megváltoztatná a program megfigyelhető működését. Éppen ezért a C++ programozónak tudnia kell, hogy mikor van garantált viselkedés, és mikor bízik a fordító „jóindulatában”.