Udostępnij za pośrednictwem


Zrozumienie SAL

Język opisu kodu źródłowego Microsoft (SAL) udostępnia zestaw adnotacje, które służą do opisywania, jak funkcja wykorzystuje jego parametry, założeń, które to sprawia, że na ich temat i gwarantuje, że to sprawia, że po jego zakończeniu.Adnotacje są zdefiniowane w pliku nagłówkowym <sal.h>.Analizy kodu Visual Studio c++ użyto adnotacje SAL, aby zmodyfikować jego analiza funkcji.Aby uzyskać więcej informacji na temat SAL 2.0 dla systemu Windows driver development zobacz SAL 2.0 adnotacje dla sterowników systemu Windows.

Natywnie C i C++ zawierają tylko ograniczone możliwości dla deweloperów do konsekwentnie wyrażania intencji i działanie.Za pomocą adnotacji SAL, możesz opisać swoje funkcje, bardziej szczegółowo tak, aby deweloperzy, którzy zużywają je lepiej zrozumieć sposób ich użycia.

Co to jest SAL i dlaczego należy go użyć?

Krótko mówiąc, SAL jest niedrogi sposób, aby kompilator sprawdzić kod dla Ciebie.

Hh916383.collapse_all(pl-pl,VS.110).gifSAL czyni kod bardziej wartościowe

SAL może pomóc swój projekt kod bardziej zrozumiałe, zarówno dla ludzi, jak i dla narzędzi do analizy kodu.Ten przykład, pokazujący funkcji środowiska wykonawczego języka C memcpy:

void * memcpy(
   void *dest, 
   const void *src, 
   size_t count
);

Można powiedzieć, ta funkcja nie?Gdy wprowadzane w życie lub wywołana funkcja niektórych właściwości muszą być utrzymywane do zapewnienia poprawności programu.Rzut deklarację taką jak w przykładzie, nie wiem, co to są.Bez adnotacje SAL trzeba polegać na dokumentacji lub komentarzy do kodu.Oto co w dokumentacji MSDN dla memcpy mówi:

"Kopie zliczanie bajtów src do dest.Jeśli źródłowy i docelowy zachodzą na siebie, zachowanie memcpy jest niezdefiniowany.Do obsługi pokrywających się obszarów, należy użyć memmove.Uwaga dotycząca bezpieczeństwa: upewnij się, że bufor docelowy jest taki sam lub większy rozmiar niż bufor źródłowy.Aby uzyskać więcej informacji zobacz unikanie przekroczeniem buforu."

Dokumentacja zawiera kilka bitów informacji, które sugerują, że w kodzie jest utrzymanie niektórych właściwości w celu zapewnienia poprawności programu:

  • memcpykopie count bajtów ze źródłowego buforu do buforu docelowego.

  • Bufor docelowy musi być co najmniej równym rozmiarowi buforu źródłowego.

Jednak kompilator nie może odczytać dokumentacji lub nieformalne komentarze.Nie wie, że istnieje relacja między dwa bufory i count, i to również nie można skutecznie zgadywać o relacji.SAL może zapewnić większą przejrzystość o właściwościach i implementację funkcji, jak pokazano poniżej:

void * memcpy(
   _Out_writes_bytes_all_(count) void *dest, 
   _In_reads_bytes_(count) const void *src, 
   size_t count
);

Należy zauważyć, że adnotacje te przypominają informacje zawarte w dokumentacji MSDN, ale są one bardziej zwięzłe i one zgodnie z określonym wzorcem semantycznego.Jeśli czytasz ten kod, można szybko zrozumieć właściwości tej funkcji i jak uniknąć problemów z zabezpieczeniami przekroczenie buforu.Jeszcze lepiej wzorce semantyczne, które zapewnia SAL może poprawić skuteczność oraz efektywność narzędzia do analizy kodu zautomatyzowanych w wczesnego wykrywania potencjalnych błędów.Wyobraźmy sobie, że ktoś zapisuje ten wadliwy wykonania wmemcpy:

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;
}

Ta implementacja zawiera typowy błąd po drugim.Na szczęście Autor kod wprowadził adnotacji rozmiar buforu SAL — narzędzie do analizy kodu mogłyby zaczepić błąd analizując ta sama funkcja.

Hh916383.collapse_all(pl-pl,VS.110).gifPodstawowe informacje o SAL

SAL definiuje cztery podstawowe typy parametrów, które są uporządkowane według wzorek użycia.

Kategoria

Parametr adnotacji

Opis

Dane wejściowe do wywołał funkcję

_In_

Danych jest przekazywany do funkcji o nazwie, a jest traktowana jako tylko do odczytu.

Dane wejściowe do wywołał funkcję, a następnie utworzyć obiekt wywołujący

_Inout_

Można używać danych jest przekazywany do funkcji i potencjalnie jest modyfikowany.

Dane wyjściowe do wywołującego

_Out_

Obiekt wywołujący tylko miejsce wywołana funkcja do zapisu.Wywoływana funkcja zapisuje dane do tej przestrzeni.

Dane wyjściowe wskaźnik do wywołującego

_Outptr_

Jak dane wyjściowe do wywołującego.Wartość zwracana przez funkcję o nazwie jest wskaźnik.

Adnotacje te cztery podstawowe mogą być wyraźniej w różny sposób.Domyślnie parametry adnotacjami wskaźnik są przyjmowane jako wymagany — muszą być inne niż NULL dla funkcji została wykonana pomyślnie.Najczęściej używane zmiany podstawowych adnotacje wskazuje, że parametr wskaźnika jest opcjonalne — Jeśli ma wartość NULL, funkcja nadal mogą odnieść sukces w swojej pracy.

Ta tabela pokazuje, jak odróżnić wymaganych i opcjonalnych parametrów:

Parametry są wymagane

Parametry są opcjonalne

Dane wejściowe do wywołał funkcję

_In_

_In_opt_

Dane wejściowe do wywołał funkcję, a następnie utworzyć obiekt wywołujący

_Inout_

_Inout_opt_

Dane wyjściowe do wywołującego

_Out_

_Out_opt_

Dane wyjściowe wskaźnik do wywołującego

_Outptr_

_Outptr_opt_

Adnotacje te ułatwiają identyfikację możliwych wartości niezainicjowanej i nieprawidłowy wskaźnik zerowy używa w sposób formalny i dokładne.Przekazanie wartości NULL do wymaganego parametru może spowodować awarię, lub może spowodować, że kod błędu "nie powiodło się" mają być zwrócone.Tak czy inaczej, funkcja nie są w stanie w wykonuje swoją pracę.

Przykłady SAL

W tej części przedstawiono przykłady kodu służącego do podstawowych adnotacje SAL.

Hh916383.collapse_all(pl-pl,VS.110).gifZa pomocą Visual Studio narzędzie do analizy kodu, aby znaleźć wady

W przykładach narzędzie Visual Studio kod analizy służy wraz z adnotacjami SAL znaleźć kod wad.Oto jak to zrobić.

Aby użyć narzędzia do analizy kodu Visual Studio i SAL

  1. W programie Visual Studio Otwórz projekt języka C++, który zawiera adnotacje SAL.

  2. Na pasku menu wybierz polecenie budować, Uruchomić analizy kodu na rozwiązanie.

    Rozważmy przykład _In_ w tej sekcji.Po uruchomieniu analizy kodu na nim, to ostrzeżenie jest wyświetlane:

    C6387 Nieprawidłowa wartość parametru"piwo" może być "0": to nie pasuje do specyfikacji funkcji 'InCallee'.

Hh916383.collapse_all(pl-pl,VS.110).gifPrzykład: _In_ adnotacji

_In_ Adnotacji wskazuje, że:

  • Parametr musi być prawidłowy i nie będzie modyfikowana.

  • Funkcja będzie tylko odczytać z buforu pojedynczego elementu.

  • Obiekt wywołujący musi dostarczyć bufor i go zainicjować.

  • _In_Określa "tylko do odczytu".Częstym błędem jest zastosowanie _In_ z parametrem, który powinien mieć _Inout_ adnotacji zamiast.

  • _In_jest dozwolone, ale ignorowane przez analizatora na nie wskaźnik funkcji skalarnych.

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
}

Jeśli używasz programu Visual Studio kod analizy na tym przykładzie sprawdza, że rozmówców przekazać Null wskaźnikiem do zainicjowanej buforu dla pInt.W takim przypadku pInt wskaźnika nie może być NULL.

Hh916383.collapse_all(pl-pl,VS.110).gifPrzykład: _In_opt_ adnotacji

_In_opt_jest taka sama jak _In_, z tym, że parametr wejściowy może być NULL, i w związku z tym, funkcja należy sprawdzić, czy to.

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);
} 

Visual Studio kod analizy potwierdza, że funkcja sprawdza na wartości NULL, zanim uzyska dostęp do buforu.

Hh916383.collapse_all(pl-pl,VS.110).gifPrzykład: _Out_ adnotacji

_Out_obsługuje typowy scenariusz, w którym jest przekazywana NULL wskaźnikiem, który wskazuje na bufor element i funkcja inicjuje element.Obiekt wywołujący nie ma zainicjować bufora przed wywołaniem; wywoływana funkcja obietnice przed wyświetleniem go zainicjować.

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;
} 

Narzędzie do analizy kodu Visual Studio sprawdza, czy wywołujący NULL wskaźnikiem do buforu dla pInt i że bufor jest inicjowany przez funkcję, przed wyświetleniem.

Hh916383.collapse_all(pl-pl,VS.110).gifPrzykład: _Out_opt_ adnotacji

_Out_opt_jest taka sama jak _Out_, z tym, że parametr może być NULL, i w związku z tym, funkcja należy sprawdzić, czy to.

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);
} 

Visual Studio kod analizy potwierdza, że funkcja ta sprawdza się na wartości NULL, przed pInt wyświetlono i jeśli pInt nie jest NULL, że bufor jest inicjowany przez funkcję, przed wyświetleniem.

Hh916383.collapse_all(pl-pl,VS.110).gifPrzykład: _Inout_ adnotacji

_Inout_Służy do dodawania adnotacji parametr wskaźnika, który może być zmieniony przez funkcję.Wskaźnik musi wskazywać prawidłowe zainicjować danych przed wywołaniem, a nawet jeśli ulegnie zmianie, to muszą mieć prawidłową wartość dla zwrotów.Adnotacja Określa, czy funkcja może swobodnie odczytać i zapisać w buforze jeden element.Obiekt wywołujący musi dostarczyć bufor i go zainicjować.

[!UWAGA]

Jak _Out_, _Inout_ muszą być stosowane do wartości można modyfikować.

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
} 

Visual Studio kod analizy potwierdza, że dzwoniący przekazać NULL wskaźnikiem do zainicjowanej buforu dla pIntoraz że przed powrotu, pInt jest nadal NIEZEROWE i inicjowany jest bufor.

Hh916383.collapse_all(pl-pl,VS.110).gifPrzykład: _Inout_opt_ adnotacji

_Inout_opt_jest taka sama jak _Inout_, z tym, że parametr wejściowy może być NULL, i w związku z tym, funkcja należy sprawdzić, czy to.

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);
} 

Visual Studio kod analizy potwierdza, że ta funkcja sprawdza na wartości NULL, zanim uzyska dostęp do buforu, a jeśli pInt nie jest NULL, że bufor jest inicjowany przez funkcję, przed wyświetleniem.

Hh916383.collapse_all(pl-pl,VS.110).gifPrzykład: _Outptr_ adnotacji

_Outptr_Służy do dodawania adnotacji parametr, który ma przeznaczone do zwracania wskaźnik.Parametru, sam nie powinna być równa NULL i wywołana funkcja zwraca NULL wskaźnikiem w nim i że wskaźnik wskazuje zainicjować danych.

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);
} 

Visual Studio kod analizy sprawdza, czy wywołujący NULL wskaźnikiem *pInt, oraz że bufor jest inicjowany przez funkcję, przed wyświetleniem.

Hh916383.collapse_all(pl-pl,VS.110).gifPrzykład: _Outptr_opt_ adnotacji

_Outptr_opt_jest taka sama jak _Outptr_, z tym, że parametr jest opcjonalny — w wywołaniu można przekazać w wskaźnik o wartości NULL dla parametru.

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);
} 

Visual Studio kod analizy potwierdza, że funkcja ta sprawdza się na wartości NULL, przed *pInt wyświetlono, oraz że bufor jest inicjowany przez funkcję, przed wyświetleniem.

Hh916383.collapse_all(pl-pl,VS.110).gifPrzykład: _Success_ adnotacji w połączeniu z _Out_

Adnotacje mogą być stosowane do większości obiektów.W szczególności można dodać adnotacje całej funkcji.Jest jedną z najbardziej oczywistych cech, funkcji, że powiodła się lub zakończyć się niepowodzeniem.Ale jak skojarzenie między buforu i jego rozmiar, C/C++ nie można wyrazić funkcja sukces lub niepowodzenie.Za pomocą _Success_ adnotacji, można powiedzieć, jakiego sukcesu dla funkcji wygląda jak.Parametr do _Success_ adnotacja jest po prostu wyrażenie że kiedy to PRAWDA wskazuje, że funkcja została pomyślnie.Wyrażenie może być cokolwiek, jaką może obsłużyć parser adnotacji.Skutki adnotacje, po powrocie z funkcji są stosowane tylko w przypadku, gdy funkcja się powiedzie.W tym przykładzie przedstawiono sposób _Success_ współdziała z _Out_ na ten problem.Można użyć słowa kluczowego return do reprezentowania wartości zwracanej.

_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;
   }
}

_Out_ Adnotacji powoduje analizy kodu Visual Studio do sprawdzania poprawności, że wywołujący NULL wskaźnikiem do buforu dla pInt, oraz że bufor jest inicjowany przez funkcję, przed wyświetleniem.

SAL najlepszej praktyki

Hh916383.collapse_all(pl-pl,VS.110).gifDodawanie adnotacji do istniejącego kodu

SAL to potężne technologia, która może pomóc poprawić bezpieczeństwo i niezawodność kodu.Po dowiedzieć się, SAL, nowe umiejętności można zastosować do codziennej pracy.W nowym kodzie można stosować specyfikacje oparte na SAL, zgodnie z projektem przez cały; w starszych kodu można stopniowo dodawać adnotacje i tym samym zwiększyć korzyści za każdym razem, gdy zaktualizujesz.

Microsoft publiczne nagłówki są już przypisane.Dlatego zaleca się, że w projektach najpierw opisywania funkcji węzeł liścia i funkcje, które wywołują Win32 API, aby maksymalnie wykorzystać zalety.

Hh916383.collapse_all(pl-pl,VS.110).gifKiedy adnotacji

Poniżej przedstawiono wskazówki:

  • Opisywanie wszystkie parametry wskaźnika.

  • Dodawanie adnotacji adnotacje zakres wartości, aby umożliwić analizy kodu buforu i wskaźnik bezpieczeństwa.

  • Opisywanie reguł blokowania i blokowania niepożądanych.Aby uzyskać więcej informacji, zobacz Dodawanie adnotacji do zachowania blokującego.

  • Opisywanie właściwości sterownika i inne właściwości specyficzne dla domeny.

Lub można dodać adnotacje wszystkie parametry, aby się jasno konwersji całej i aby był łatwy do sprawdzenia, że adnotacje zostały wykonane.

Zasoby pokrewne

Blog zespołu analizy kodu

Zobacz też

Informacje

Dodawanie adnotacji do parametrów funkcji i zwracanych wartości

Zachowanie funkcji dodawania adnotacji

Dodawanie adnotacji struktur i klas

Dodawanie adnotacji do zachowania blokującego

Określanie warunków pojawiania się adnotacji

Najlepsze praktyki i przykłady (SAL)

Inne zasoby

Za pomocą adnotacji SAL do zmniejszenia wady kod C/C++