2013-04-15 24 views
5

Istnieje kilka implementacji funkcji printf szablonów variadic. Jednym z nich jest to:Variadic szablony i typy zabezpieczeń

void printf(const char* s) { 
    while (*s) { 
    if (*s == '%' && *++s != '%') 
     throw std::runtime_error("invalid format string: missing arguments"); 
    std::cout << *s++; 
    } 
} 

template<typename T, typename... Args> 
void printf(const char* s, const T& value, const Args&... args) { 
    while (*s) { 
    if (*s == '%' && *++s != '%') { 
     std::cout << value; 
     return printf(++s, args...); 
    } 
    std::cout << *s++; 
    } 
    throw std::runtime_error("extra arguments provided to printf"); 
} 

i wszędzie jest powiedziane, że ta implementacja jest typu bezpieczne podczas normalnej C (przy zmiennej liczbie argumentów argumenty va_arg) nie jest.

Dlaczego tak jest? Co to znaczy być bezpiecznym w typie i jakie zalety ma to wdrożenie w porównaniu z printf va_arg?

+3

Ta wersja nie dba w ogóle o flagi formatu, po prostu drukuje rzeczy za pośrednictwem operatorów strumieniowych. – Xeo

+1

Jest to typ bezpieczny, ponieważ 'T' zawsze będzie typem faktycznie przekazanego parametru. Standardowy printf nie wie. –

+1

Na marginesie, jest to okropna implementacja 'printf' przy wykonywaniu typów.Ignoruje i źle interpretuje specyfikatory formatu, a nawet nie obsługuje przenoszenia wartości tymczasowych! W skrócie, jest to funkcja "safe", ale nie jest to poprawna implementacja 'printf', pomimo swojej nazwy. Dobry "printf" działający pod kątem bezpieczeństwa będzie zachowywał się identycznie jak "printf", gdy będzie bezpieczny i nie powiedzie się niezdefiniowany, kiedy będzie w większości niebezpiecznych sytuacji. – Yakk

Odpowiedz

4

Będąc bezpieczne lub typu bezpiecznego oznacza, że ​​można powiedzieć, ze patrząc na kodzie źródłowym czy program zachowuje się poprawnie.

Oświadczenie std::cout << x jest zawsze poprawne, zakładając, że x ma dobrze zdefiniowaną wartość (i nie jest, powiedzmy, niezainicjalizowaną); to jest coś, co można zagwarantować patrząc na kod źródłowy.

przez constrast C jest nie bezpieczny, na przykład następujące kod może lub nie może być prawdziwe, zależności od wejścia wykonawczego:

int main(int argc, char * argv[]) 
{ 
    if (argc == 3) 
     printf(argv[1], argv[2]); 
} 

jest prawidłowy, wtedy i tylko wtedy, gdy Pierwszym argumentem jest poprawny ciąg formatujący zawierający dokładnie jeden "%s".

Innymi słowy, możliwe jest napisanie poprawnego programu C, ale nie można wnioskować o poprawności tylko poprzez sprawdzenie kodu. Jednym z takich przykładów jest funkcja printf. Mówiąc bardziej ogólnie, każda funkcja akceptująca zmienne argumenty jest najprawdopodobniej niebezpieczna, podobnie jak każda funkcja, która rzuca wskaźniki na podstawie wartości środowiska wykonawczego.

+0

+1 Trudno pobić tę próbkę, aby uzyskać lepszy stan wykonania zależny od środowiska wykonawczego. I znakomita odpowiedź na to. – WhozCraig

5

Dla wszystkich argumentów przekazywanych do wersji szablonu variadic, ich typy są znane w czasie kompilacji. Ta wiedza jest zachowana w funkcji. Każdy obiekt jest następnie przekazywany do cout z mocno przeciążonym operator<<. Dla każdego przekazywanego typu istnieje oddzielne przeciążenie tej funkcji. Innymi słowy, jeśli zdasz int, to wywołanie ostream::operator<<(int), jeśli przekażesz podwójne, to wywołanie ostream::operator<<(double). Więc znowu, typ jest zachowany. Każda z tych funkcji specjalizuje się w obsłudze każdego typu w odpowiedni sposób. To jest bezpieczeństwo typu.

Jednak historia C printf jest inna. Typ nie jest zachowywany wewnątrz funkcji. Musi to obliczyć na podstawie zawartości ciągu formatu (który może być wartością wykonawczą). Funkcja musi jedynie przyjąć, że poprawny ciąg formatów został przekazany, aby dopasować typy argumentów. Kompilator nie wymusza tego.

Istnieje również inny rodzaj bezpieczeństwa, a to z powodu liczby argumentów. Jeśli przekazujesz zbyt mało argumentów do funkcji C printf, nie wystarczy, aby dopasować ciąg formatu, masz niezdefiniowane zachowanie. Jeśli zrobisz to samo z szablonem variadic, otrzymasz wyjątek, który, choć nie jest pożądany, jest o wiele łatwiejszy do zdiagnozowania.