Jegyzet
Az oldalhoz való hozzáférés engedélyezést igényel. Próbálhatod be jelentkezni vagy könyvtárat váltani.
Az oldalhoz való hozzáférés engedélyezést igényel. Megpróbálhatod a könyvtár váltását.
A Microsoft forráskódos széljegyzetnyelve (SAL) olyan széljegyzeteket biztosít, amelyekkel leírhatja, hogy egy függvény hogyan használja a paramétereit, milyen feltételezéseket tesz ezekről, és milyen garanciákat biztosít, amikor befejeződik. A széljegyzetek <sal.h>fejlécfájlban vannak definiálva. A C++ Visual Studio kódelemzése SAL-széljegyzetekkel módosítja a függvények elemzését. A Windows-illesztőprogramok fejlesztéséhez készült SAL 2.0-ról további információt a Windows-illesztőprogramok SAL 2.0-s széljegyzetei című témakörben talál.
Natív módon a C és a C++ csak korlátozott lehetőségeket biztosít a fejlesztők számára a szándék és az állandóság következetes kifejezésére. A SAL-széljegyzetek használatával részletesebben is leírhatja a függvényeket, hogy az őket használó fejlesztők jobban megértsék a használatukat.
Mi az a SAL és miért érdemes használni?
Egyszerűen fogalmazva, az SAL egy olcsó módja annak, hogy a fordító ellenőrizze a kódot.
Az SAL értékesebbé teszi a kódot
A SAL segítségével érthetőbbé teheti a kódtervezést mind az emberek, mind a kódelemzési eszközök számára. Tekintse meg ezt a példát, amely a C futtatókörnyezet függvényt memcpymutatja be:
void * memcpy(
void *dest,
const void *src,
size_t count
);
Meg tudja állapítani, hogy ez a függvény mit csinál? Ha egy függvényt implementál vagy meghív, bizonyos tulajdonságokat fenn kell tartani a program helyességének biztosítása érdekében. Csak egy olyan deklarációt tekintve, mint a példában, nem tudja, mik ezek. SAL-széljegyzetek nélkül dokumentációra vagy kód megjegyzéseire kell támaszkodnia. A dokumentáció a memcpy-ról a következőket mondja:
"
memcpyátmásolja count bájtok számát a forrásból a destbe;wmemcpyátmásolja count széles karaktert (két bájtot)." Ha a forrás és a cél átfedésben van, a rendszer nem definiálja a viselkedéstmemcpy. Az átfedésben lévő régiók kezelésére használhatómemmove.
Fontos: Győződjön meg arról, hogy a célpuffer mérete megegyezik vagy nagyobb, mint a forráspuffer. További információ: Puffertúllépések elkerülése."
A dokumentáció néhány bitnyi információt tartalmaz, amelyek arra utalnak, hogy a kódnak bizonyos tulajdonságokat kell fenntartania a program helyességének biztosítása érdekében:
memcpyátmásolja a bájtokat acountforráspufferből a célpufferbe.A célpuffernek legalább akkoranak kell lennie, mint a forráspuffer.
A fordító azonban nem tudja elolvasni a dokumentációt vagy az informális megjegyzéseket. Nem tudja, hogy van-e kapcsolat a két puffer countközött, és nem tud hatékonyan kitalálni egy kapcsolatot. A SAL a függvény tulajdonságaival és implementálásával kapcsolatos egyértelműbb információkat nyújthat, ahogy az itt látható:
void * memcpy(
_Out_writes_bytes_all_(count) void *dest,
_In_reads_bytes_(count) const void *src,
size_t count
);
Figyelje meg, hogy ezek a széljegyzetek hasonlítanak a dokumentációban szereplő információkra, de tömörebbek, és szemantikai mintát követnek. A kód elolvasásakor gyorsan megértheti a függvény tulajdonságait, és hogy miként kerülheti el a puffertúlcsordulás biztonsági problémáit. Még jobb, hogy a szemantikai minták, amelyeket az SAL nyújt, javíthatják az automatizált kódelemzési eszközök hatékonyságát és eredményességét a potenciális hibák korai felfedezésében. Tegyük fel, hogy valaki a következő hibás implementációt wmemcpyírja:
wchar_t * wmemcpy(
_Out_writes_all_(count) wchar_t *dest,
_In_reads_(count) const wchar_t *src,
size_t count)
{
size_t i;
for (i = 0; i <= count; i++) { // BUG: off-by-one error
dest[i] = src[i];
}
return dest;
}
Ez az implementáció egy gyakori, egyszeri hibát tartalmaz. Szerencsére a kód szerzője tartalmazta a SAL pufferméret-megjegyzést – a kódelemző eszköz egyedül a függvény elemzésével tudta elkapni a hibát.
Az SAL alapjai
A SAL négy alapvető paramétertípust határoz meg, amelyeket használati minta szerint kategorizál.
| Kategória | Paraméter széljegyzete | Leírás |
|---|---|---|
| Bemenet a hívott függvényhez | _In_ |
Az adatok továbbítva lesznek a hívott függvénynek, és írásvédettként lesznek kezelve. |
| Bemenet a hívott függvényhez, és kimenet a hívónak | _Inout_ |
A használható adatok továbbítva lesznek a függvénybe, és potenciálisan módosulnak. |
| Kimenet a hívónak | _Out_ |
A hívó csak helyet biztosít a hívott függvénynek az íráshoz. A hívott függvény adatokat ír ebbe a térbe. |
| Mutató kimenete a hívónak | _Outptr_ |
Például a hívó kimenete. A hívott függvény által visszaadott érték egy mutató. |
Ez a négy alapjegyzet többféleképpen is explicitebbé tehető. Alapértelmezés szerint a jegyzetekkel ellátott mutatóparaméterek kötelezőek – a függvény sikeréhez nem null értékűnek kell lenniük. Az alapjegyzetek leggyakrabban használt változata azt jelzi, hogy a mutatóparaméter nem kötelező – null érték esetén a függvény továbbra is sikeresen elvégezheti a munkáját.
Ez a táblázat bemutatja, hogyan lehet különbséget tenni a szükséges és a választható paraméterek között:
| Paraméterekre van szükség | A paraméterek megadása nem kötelező | |
|---|---|---|
| Bemenet a hívott függvényhez | _In_ |
_In_opt_ |
| Bemenet a hívott függvényhez, és kimenet a hívónak | _Inout_ |
_Inout_opt_ |
| Kimenet a hívónak | _Out_ |
_Out_opt_ |
| Mutató kimenete a hívónak | _Outptr_ |
_Outptr_opt_ |
Ezek a széljegyzetek segítenek azonosítani a lehetséges nem inicializált értékeket, valamint az érvénytelen null mutató formális és pontos használatát. A NULL megadása egy szükséges paraméternek összeomlást okozhat, vagy "sikertelen" hibakód visszaadását okozhatja. Akárhogy is, a függvény nem tudja elvégezni a feladatát.
SAL-példák
Ez a szakasz az alapszintű SAL-széljegyzetek kód példáit mutatja be.
Hibák keresése a Visual Studio kódelemző eszközével
A példákban a Visual Studio kódelemző eszközét SAL-széljegyzetekkel együtt használjuk a kódhibák kereséséhez. Ezt a következőképpen teheti meg.
Visual Studio-kódelemzési eszközök és SAL használata
A Visual Studióban nyisson meg egy C++ projektet, amely SAL-széljegyzeteket tartalmaz.
A menüsávon válassza a Build, a Kódelemzés futtatása a megoldáson lehetőséget.
Tekintse meg az ebben a szakaszban található _In_ példát. Ha kódelemzést futtat rajta, ez a figyelmeztetés jelenik meg:
C6387 Érvénytelen paraméterérték A "pInt" lehet "0": ez nem felel meg az "InCallee" függvény specifikációjának.
Példa: Az _In_ jelölés
A _In_ széljegyzet a következőt jelzi:
A paraméternek érvényesnek kell lennie, és nem módosítható.
A függvény csak az egyelemes pufferből lesz beolvasva.
A hívónak meg kell adnia a puffert, és inicializálnia kell azt.
_In_az "írásvédett" értéket adja meg. Gyakori hiba, hogy olyan paraméterre kell alkalmazni_In_, amely helyett a_Inout_széljegyzetnek kell lennie._In_a nem mutató skalárok esetében engedélyezett, de az elemző figyelmen kívül hagyja.
void InCallee(_In_ int *pInt)
{
int i = *pInt;
}
void GoodInCaller()
{
int *pInt = new int;
*pInt = 5;
InCallee(pInt);
delete pInt;
}
void BadInCaller()
{
int *pInt = NULL;
InCallee(pInt); // pInt should not be NULL
}
Ha Visual Studio-kódelemzést használ ebben a példában, az ellenőrzi, hogy a hívók átadják-e egy inicializált puffernek a nem null értékű mutatót pInt. Ebben az esetben a pInt mutató nem lehet NULL értékű.
Példa: A _In_opt_ annotáció
_In_opt_ megegyezik _In_azzal a kivétellel, hogy a bemeneti paraméter null értékű lehet, ezért a függvénynek ellenőriznie kell ezt.
void GoodInOptCallee(_In_opt_ int *pInt)
{
if(pInt != NULL) {
int i = *pInt;
}
}
void BadInOptCallee(_In_opt_ int *pInt)
{
int i = *pInt; // Dereferencing NULL pointer 'pInt'
}
void InOptCaller()
{
int *pInt = NULL;
GoodInOptCallee(pInt);
BadInOptCallee(pInt);
}
A Visual Studio kódelemzése ellenőrzi, hogy a függvény null értéket keres-e, mielőtt hozzáfér a pufferhez.
Példa: A _Out_ annotáció
_Out_ Támogat egy olyan gyakori forgatókönyvet, amelyben egy elempufferre mutató, nem NULL mutatót adnak át, és a függvény inicializálja az elemet. A hívónak nem kell inicializálnia a puffert a hívás előtt; a hívott függvény azt ígéri, hogy inicializálja azt, mielőtt visszatér.
void GoodOutCallee(_Out_ int *pInt)
{
*pInt = 5;
}
void BadOutCallee(_Out_ int *pInt)
{
// Did not initialize pInt buffer before returning!
}
void OutCaller()
{
int *pInt = new int;
GoodOutCallee(pInt);
BadOutCallee(pInt);
delete pInt;
}
A Visual Studio kódelemzése ellenőrzi, hogy a hívó egy nem NULL mutatót ad-e át egy puffernek pInt , és hogy a függvény inicializálja a puffert, mielőtt visszatér.
Példa: A _Out_opt_ annotáció
_Out_opt_ megegyezik _Out_azzal a kivételel, hogy a paraméter null értékű, ezért a függvénynek ellenőriznie kell ezt.
void GoodOutOptCallee(_Out_opt_ int *pInt)
{
if (pInt != NULL) {
*pInt = 5;
}
}
void BadOutOptCallee(_Out_opt_ int *pInt)
{
*pInt = 5; // Dereferencing NULL pointer 'pInt'
}
void OutOptCaller()
{
int *pInt = NULL;
GoodOutOptCallee(pInt);
BadOutOptCallee(pInt);
}
A Visual Studio kódelemzése megerősíti, hogy a függvény ellenőrzi, hogy pInt értéke NULL-e, mielőtt hivatkozna rá, és ha pInt nem NULL, akkor a függvény inicializálja a puffert, mielőtt visszatérne.
Példa: Az _Inout_ annotáció
_Inout_ olyan mutatóparaméter megjegyzésére szolgál, amelyet a függvény esetleg módosíthat. Az egérmutatónak a hívás előtt érvényes inicializált adatokra kell mutatnia, és még ha változik is, akkor is érvényes értékkel kell rendelkeznie. A széljegyzet azt határozza meg, hogy a függvény szabadon olvashat és írhat az egyelemes pufferbe. A hívónak meg kell adnia a puffert, és inicializálnia kell azt.
Megjegyzés
Az _Out_-hoz hasonlóan a _Inout_-nek is egy módosítható értékre kell vonatkoznia.
void InOutCallee(_Inout_ int *pInt)
{
int i = *pInt;
*pInt = 6;
}
void InOutCaller()
{
int *pInt = new int;
*pInt = 5;
InOutCallee(pInt);
delete pInt;
}
void BadInOutCaller()
{
int *pInt = NULL;
InOutCallee(pInt); // 'pInt' should not be NULL
}
A Visual Studio kódelemzése ellenőrzi, hogy a hívók egy nem NULL mutatót adnak át egy inicializált pufferhez pInt, és hogy a visszatérés előtt pInt továbbra is nem NULL értékű, és a puffer inicializálva van.
Példa: A _Inout_opt_ annotáció
_Inout_opt_ megegyezik _Inout_azzal a kivétellel, hogy a bemeneti paraméter null értékű lehet, ezért a függvénynek ellenőriznie kell ezt.
void GoodInOutOptCallee(_Inout_opt_ int *pInt)
{
if(pInt != NULL) {
int i = *pInt;
*pInt = 6;
}
}
void BadInOutOptCallee(_Inout_opt_ int *pInt)
{
int i = *pInt; // Dereferencing NULL pointer 'pInt'
*pInt = 6;
}
void InOutOptCaller()
{
int *pInt = NULL;
GoodInOutOptCallee(pInt);
BadInOutOptCallee(pInt);
}
A Visual Studio kódelemzése ellenőrzi, hogy ez a függvény a NULL értéket ellenőrzi-e, mielőtt hozzáfér a pufferhez, és ha pInt nem NULL, akkor a függvény inicializálja a puffert, mielőtt visszatér.
Példa: A _Outptr_ annotáció
_Outptr_ olyan paraméter megjegyzésére szolgál, amely egy mutató visszaadására szolgál. Maga a paraméter nem lehet NULL, és a hívott függvény egy nem NULL mutatót ad vissza benne, és ez a mutató inicializált adatokra mutat.
void GoodOutPtrCallee(_Outptr_ int **pInt)
{
int *pInt2 = new int;
*pInt2 = 5;
*pInt = pInt2;
}
void BadOutPtrCallee(_Outptr_ int **pInt)
{
int *pInt2 = new int;
// Did not initialize pInt buffer before returning!
*pInt = pInt2;
}
void OutPtrCaller()
{
int *pInt = NULL;
GoodOutPtrCallee(&pInt);
BadOutPtrCallee(&pInt);
}
A Visual Studio kódelemzése ellenőrzi, hogy a hívó egy nem NULL mutatót *pIntad-e át, és hogy a függvény inicializálja a puffert, mielőtt visszatér.
Példa: A _Outptr_opt_ jegyzet
_Outptr_opt_ ugyanaz, mint _Outptr_– kivéve, hogy a paraméter nem kötelező – a hívó null mutatót adhat a paraméterhez.
void GoodOutPtrOptCallee(_Outptr_opt_ int **pInt)
{
int *pInt2 = new int;
*pInt2 = 6;
if(pInt != NULL) {
*pInt = pInt2;
}
}
void BadOutPtrOptCallee(_Outptr_opt_ int **pInt)
{
int *pInt2 = new int;
*pInt2 = 6;
*pInt = pInt2; // Dereferencing NULL pointer 'pInt'
}
void OutPtrOptCaller()
{
int **ppInt = NULL;
GoodOutPtrOptCallee(ppInt);
BadOutPtrOptCallee(ppInt);
}
A Visual Studio kódelemzése ellenőrzi, hogy ez a függvény a null *pInt értéket ellenőrzi-e a dereferencia előtt, és hogy a függvény inicializálja a puffert, mielőtt visszatér.
Példa: A _Success_ annotáció kombinálva az _Out_ -tal
A széljegyzetek a legtöbb objektumra alkalmazhatók. Különösen, egy egész függvényt is kommentelhet. A függvények egyik legnyilvánvalóbb jellemzője, hogy sikeresek vagy sikertelenek lehetnek. A puffer és a mérete közötti társításhoz hasonlóan a C/C++ nem tudja kifejezni a függvény sikerességét vagy meghibásodását. A _Success_ jelölés használatával meghatározhatja, hogy milyen egy függvény sikeres működése. Az _Success_ annotáció paramétere csupán egy olyan kifejezés, amelyik igaz, amikor azt jelzi, hogy a függvény sikerült. A kifejezés bármi lehet, amit a széljegyzetelemző kezelni tud. A függvény visszatérését követő széljegyzetek hatása csak akkor alkalmazható, ha a függvény sikeres. Ez a példa bemutatja, hogyan _Success_ működik együtt _Out_ a helyes dolgot elérve. A kulcsszóval return a visszatérési értéket jelölheti.
_Success_(return != false) // Can also be stated as _Success_(return)
bool GetValue(_Out_ int *pInt, bool flag)
{
if(flag) {
*pInt = 5;
return true;
} else {
return false;
}
}
A _Out_ megjegyzés hatására a Visual Studio kódelemzése ellenőrzi, hogy a hívó egy nem NULL mutatót ad-e át egy puffernek pInt, és hogy a függvény inicializálja a puffert, mielőtt visszatér.
SAL legjobb gyakorlat
Széljegyzetek hozzáadása meglévő kódhoz
A SAL egy hatékony technológia, amely segíthet a kód biztonságának és megbízhatóságának javításában. A SAL elsajátítása után alkalmazhatja az új készséget a napi munkájára. Az új kódban az SAL-alapú specifikációkat a teljes terv szerint használhatja; a régebbi kódban növekményesen adhat hozzá széljegyzeteket, és ezáltal minden frissítéskor növelheti az előnyöket.
A Nyilvános Microsoft-fejlécek már széljegyzetekkel vannak eljegyzve. Ezért javasoljuk, hogy a legnagyobb előny eléréséhez a projektjeiben először jelölje meg a levélcsomópont-függvényeket és azokat a függvényeket, amelyek Win32 API-kat hívnak.
Mikor jegyzetelek?
Íme néhány irányelv:
Jegyzetelje az összes mutatóparamétert.
Jegyzetelje az értéktartomány-széljegyzeteket, hogy a kódelemzés biztosítsa a puffer és a mutató biztonságát.
Széljegyzetek a zárolási szabályok és a zárolási mellékhatások. A további információkért lásd: A zárolási viselkedés annotálása.
Megjegyzést fűz az illesztőprogram tulajdonságaihoz és más tartományspecifikus tulajdonságokhoz.
Megjegyzéseket is fűzhet az összes paraméterhez, hogy a szándéka teljes egészében egyértelmű legyen, és hogy könnyen ellenőrizze, hogy a széljegyzetek elkészültek-e.
Lásd még
- C/C++ kódhibák csökkentése SAL-széljegyzetek használatával
- Függvényparaméterek és visszatérési értékek megjegyzése
- A függvény viselkedésének megjegyzésekkel való ellátása
- Szerkezetek és osztályok jegyzetelése
- A zárolási viselkedés megjegyzésekkel való ellátása
- Meghatározhatja, hogy mikor és hol alkalmazandó egy megjegyzés
- Ajánlott eljárások és példák