Jak przekazać zmienną liczbę argumentów do printf / sprintf

83

Mam klasę, która zawiera funkcję „błędu”, która sformatuje jakiś tekst. Chcę zaakceptować zmienną liczbę argumentów, a następnie sformatować je za pomocą printf.

Przykład:

class MyClass
{
public:
    void Error(const char* format, ...);
};

Metoda Error powinna przyjąć parametry, wywołać printf / sprintf w celu sformatowania go, a następnie coś z tym zrobić. Nie chcę sam pisać całego formatowania, więc warto spróbować dowiedzieć się, jak użyć istniejącego formatowania.

user5722
źródło

Odpowiedzi:

152
void Error(const char* format, ...)
{
    va_list argptr;
    va_start(argptr, format);
    vfprintf(stderr, format, argptr);
    va_end(argptr);
}

Jeśli chcesz manipulować łańcuchem przed jego wyświetleniem i naprawdę potrzebujesz go najpierw przechowywać w buforze, użyj vsnprintfzamiast vsprintf. vsnprintfzapobiegnie przypadkowemu błędowi przepełnienia bufora.

John Kugelman
źródło
37

spójrz na vsnprintf, ponieważ zrobi to, co chcesz http://www.cplusplus.com/reference/clibrary/cstdio/vsprintf/

będziesz musiał najpierw zainicjować tablicę argumentów va_list, a następnie ją wywołać.

Przykład z tego linku: / * vsprintf przykład * /

#include <stdio.h>
#include <stdarg.h>

void Error (char * format, ...)
{
  char buffer[256];
  va_list args;
  va_start (args, format);
  vsnprintf (buffer, 255, format, args);


  //do something with the error

  va_end (args);
}
Lodle
źródło
6
Drugim argumentem vsnprintf powinna być długość buforu, w tym kończący bajt null („\ 0”). Możesz więc użyć 256 w wywołaniu funkcji zamiast 255.
aviggiano
a przekazywanie magicznych liczb jest ZŁE ... użyj sizeof(buffer)zamiast 256.
Anonimowa
4

Powinienem był przeczytać więcej na temat istniejących pytań w przepełnieniu stosu.

C ++ Przekazywanie zmiennej liczby argumentów to podobne pytanie. Mike F ma następujące wyjaśnienie:

Nie ma sposobu na wywołanie (np.) Printf bez wiedzy, ile argumentów mu przekazujesz, chyba że chcesz wpaść w niegrzeczne i nieprzenośne sztuczki.

Powszechnie stosowanym rozwiązaniem jest zawsze zapewnienie alternatywnej formy funkcji vararg, więc printf ma vprintf, który przyjmuje va_list zamiast .... Wersje ... są po prostu opakowaniami wokół wersji va_list.

To jest dokładnie to, czego szukałem. Wykonałem taką implementację testową:

void Error(const char* format, ...)
{
    char dest[1024 * 16];
    va_list argptr;
    va_start(argptr, format);
    vsprintf(dest, format, argptr);
    va_end(argptr);
    printf(dest);
}
user5722
źródło
Końcowe „printf (dest);” jest źle sformułowany - wymaga również co najmniej ciągu formatu.
Jonathan Leffler
Nie działa, ponieważ łańcuch jest łańcuchem formatującym, tj. Printf ("ciąg"); jest w porządku
Lodle
4
Możesz uciec z printf (dest) aż do momentu, gdy dest będzie zawierał „% s” lub „% d”, a następnie BOOM . Użyj printf ("% s", dest).
John Kugelman
Chcę tylko zwrócić uwagę, że zrzut rdzenia jest najlepszym scenariuszem, zrób to w kodzie serwera, a hakerzy będą mieli twój procesor na śniadanie.
MickLH,
Spróbuj użyć „% .16383s”, a to ochroni docelową tablicę przed przepełnieniem. (zezwól na terminator '\ 0')
eddyq
3

Szukasz funkcji wariadycznych . printf () i sprintf () są funkcjami wariadycznymi - mogą przyjmować zmienną liczbę argumentów.

Zasadniczo obejmuje to następujące kroki:

  1. Pierwszy parametr musi wskazywać liczbę kolejnych parametrów. Więc w printf () parametr "format" daje takie wskazanie - jeśli masz 5 specyfikatorów formatu, to będzie szukał 5 więcej argumentów (w sumie 6 argumentów). Pierwszy argument może być liczbą całkowitą (np. "Moja funkcja (3, a, b, c) "gdzie" 3 "oznacza" 3 argumenty)

  2. Następnie przejrzyj i pobierz każdy kolejny argument, używając funkcji va_start () itp.

Istnieje wiele samouczków, jak to zrobić - powodzenia!

poundifdef
źródło
3

Używanie funkcji z elipsami nie jest zbyt bezpieczne. Jeśli wydajność nie jest krytyczna dla funkcji dziennika, rozważ użycie przeciążenia operatora, tak jak w boost :: format. Możesz napisać coś takiego:

#include <sstream>
#include <boost/format.hpp>
#include <iostream>
using namespace std;

class formatted_log_t {
public:
    formatted_log_t(const char* msg ) : fmt(msg) {}
    ~formatted_log_t() { cout << fmt << endl; }

    template <typename T>
    formatted_log_t& operator %(T value) {
        fmt % value;
        return *this;
    }

protected:
    boost::format                fmt;
};

formatted_log_t log(const char* msg) { return formatted_log_t( msg ); }

// use
int main ()
{
    log("hello %s in %d-th time") % "world" % 10000000;
    return 0;
}

Poniższy przykład ilustruje możliwe błędy z wielokropkami:

int x = SOME_VALUE;
double y = SOME_MORE_VALUE;
printf( "some var = %f, other one %f", y, x ); // no errors at compile time, but error at runtime. compiler do not know types you wanted
log( "some var = %f, other one %f" ) % y % x; // no errors. %f only for compatibility. you could write %1% instead.
Kirill V. Lyadvinsky
źródło
5
Oto jak utrudnić łatwą rzecz.
eddyq
2
„Używanie funkcji z wielokropkami nie jest zbyt bezpieczne”. jeśli jedyną bezpieczną alternatywą jest język c ++ i boost, powinieneś wyjaśnić, co masz na myśli, mówiąc „niezbyt bezpieczne” i wspomnieć, że funkcje printf są całkowicie bezpieczne, jeśli używasz poprawnych specyfikatorów formatu.
osvein
2

Prosty przykład poniżej. Zauważ, że powinieneś przekazać większy bufor i sprawdzić, czy bufor był wystarczająco duży, czy nie

void Log(LPCWSTR pFormat, ...) 
{
    va_list pArg;
    va_start(pArg, pFormat);
    char buf[1000];
    int len = _vsntprintf(buf, 1000, pFormat, pArg);
    va_end(pArg);
    //do something with buf
}
DougN
źródło