2009-02-05 18 views
12

Estoy trabajando en C++.¿Hay alguna forma de determinar cuántos caracteres escribirá sprintf?

Quiero escribir una cadena con formato potencialmente muy largo usando sprintf (específicamente una versión segura contada como _snprintf_s, pero la idea es la misma). La longitud aproximada es desconocida en el momento de la compilación, así que tendré que usar alguna memoria asignada dinámicamente en lugar de depender de un gran buffer estático. ¿Hay alguna forma de determinar cuántos caracteres se necesitarán para una llamada sprintf en particular, así puedo estar seguro de tener un buffer lo suficientemente grande?

Mi alternativa es tomar la longitud de la cadena de formato, duplicarla e intentarlo. Si funciona, genial, si no lo hace, doblaré el tamaño del búfer e intentaré de nuevo. Repita hasta que encaje. No es exactamente la solución más inteligente.

Parece que C99 admite pasar NULL a snprintf para obtener la longitud. Supongo que podría crear un módulo para ajustar esa funcionalidad si nada más, pero no estoy loco por esa idea.

Quizás un fprintf a "/ dev/null"/"nul" podría funcionar en su lugar? ¿Alguna otra idea?

EDITAR: Alternativamente, ¿hay alguna manera de "dividir" el sprintf para que retome la escritura media? Si eso es posible, podría llenar el buffer, procesarlo, luego comenzar a rellenar desde donde lo dejó.

Respuesta

0

He buscado la misma funcionalidad de la que está hablando, pero hasta donde yo sé, algo tan simple como el método C99 no está disponible en C++, porque C++ no incorpora actualmente las características agregadas en C99 (como snprintf).

Probablemente su mejor opción sea utilizar un objeto de cadena de caracteres. Es un poco más engorroso que un llamado sprintf claramente escrito, pero funcionará.

+0

No estoy seguro de por qué esto fue downvoted ... –

+0

No lo hice pero puedo ver tal vez por qué se hizo. No necesita C99 ya que hay versiones PD de snprintf() por ahí. O tal vez porque la pregunta específicamente solicitaba una solución printf() en lugar de una cadena de caracteres. No lo sé, hace tiempo que renuncié a tratar de entender a los downvoters drive-by. – paxdiablo

2

Utilizaría un enfoque de dos etapas. En general, un gran porcentaje de cadenas de salida estará por debajo de un cierto umbral y solo unas pocas serán más grandes.

Etapa 1, use un buffer estático de tamaño razonable como 4K. Como snprintf() puede restringir la cantidad de caracteres escritos, no obtendrá un desbordamiento de búfer. Lo que será se devuelve de snprintf() es la cantidad de caracteres que habría escrito si su memoria intermedia hubiera sido lo suficientemente grande.

Si su llamada a snprintf() devuelve menos de 4K, utilice el búfer y salga. Como se dijo, la gran mayoría de las llamadas deberían hacer eso.

Algunos no lo harán y es entonces cuando ingresa a la etapa 2. Si la llamada al snprintf() no cabe en el buffer 4K, al menos ahora sabe qué tan grande es el buffer que necesita.

Asignar, con malloc(), un búfer lo suficientemente grande como para mantenerlo entonces snprintf() de nuevo a ese nuevo búfer. Cuando haya terminado con el buffer, libérelo.

Trabajamos en un sistema en los días anteriores a snprintf() y obtuvimos el mismo resultado al tener un identificador de archivo conectado a /dev/null y usando fprintf() con eso./dev/null siempre garantizó tomar la cantidad de datos que usted le proporcionó, así que obtendríamos el tamaño de eso, luego asignaremos un buffer si fuera necesario.

Mantener en especie que no todos los sistemas tienen snprintf() (por ejemplo, entiendo que es _snprintf() en Microsoft C) por lo que puede que tenga que encontrar la función que hace el mismo trabajo, o volver a la solución fprintf /dev/null.

También tenga cuidado si los datos se pueden cambiar entre la comprobación de tamaño snprintf() y la snprintf() real en el búfer (es decir, wathch out for threads). Si los tamaños aumentan, obtendrá corrupción de desbordamiento de búfer.

Si sigue la regla de que los datos, una vez entregados a una función, pertenecen a esa función exclusivamente hasta su devolución, esto no será un problema.

+0

Desafortunadamente, la función snprintf() no es estándar C++. Intenté usarlo con Visual Studio 2008 Express Edition, y el compilador informa que snprintf no se encontró. – jasonmray

+0

creo que es _snprintf() en microsoft C++ – FryGuy

+0

@rubancache, que es cuando se utiliza la solución "fprintf a/dev/null". – paxdiablo

0

Eche un vistazo a CodeProject: CString-clone Using Standard C++. Utiliza la solución que sugirió con la ampliación del tamaño del búfer.

 
// ------------------------------------------------------------------------- 
    // FUNCTION: FormatV 
    //  void FormatV(PCSTR szFormat, va_list, argList); 
    //
// DESCRIPTION: // This function formats the string with sprintf style format-specs. // It makes a general guess at required buffer size and then tries // successively larger buffers until it finds one big enough or a // threshold (MAX_FMT_TRIES) is exceeded. // // PARAMETERS: // szFormat - a PCSTR holding the format of the output // argList - a Microsoft specific va_list for variable argument lists // // RETURN VALUE: // -------------------------------------------------------------------------

void FormatV(const CT* szFormat, va_list argList) 
{ 
#ifdef SS_ANSI 

    int nLen = sslen(szFormat) + STD_BUF_SIZE; 
    ssvsprintf(GetBuffer(nLen), nLen-1, szFormat, argList); 
    ReleaseBuffer(); 
#else 
    CT* pBuf   = NULL; 
    int nChars   = 1; 
    int nUsed   = 0; 
    size_type nActual = 0; 
    int nTry   = 0; 

    do 
    { 
     // Grow more than linearly (e.g. 512, 1536, 3072, etc) 

     nChars   += ((nTry+1) * FMT_BLOCK_SIZE); 
     pBuf   = reinterpret_cast<CT*>(_alloca(sizeof(CT)*nChars)); 
     nUsed   = ssnprintf(pBuf, nChars-1, szFormat, argList); 

     // Ensure proper NULL termination. 
     nActual   = nUsed == -1 ? nChars-1 : SSMIN(nUsed, nChars-1); 
     pBuf[nActual+1]= '\0'; 


    } while (nUsed < 0 && nTry++ < MAX_FMT_TRIES); 

    // assign whatever we managed to format 

    this->assign(pBuf, nActual); 
#endif 
} 

21

The man page for snprintf dice:

 
    Return value 
     Upon successful return, these functions return the number of 
     characters printed (not including the trailing '\0' used to end 
     output to strings). The functions snprintf and vsnprintf do not 
     write more than size bytes (including the trailing '\0'). If 
     the output was truncated due to this limit then the return value 
     is the number of characters (not including the trailing '\0') 
     which would have been written to the final string if enough 
     space had been available. Thus, a return value of size or more 
     means that the output was truncated. (See also below under 
     NOTES.) If an output error is encountered, a negative value is 
     returned. 

Lo que esto significa es que se puede llamar snprintf con un tamaño de 0. nada se interpondrá por escrito, y el valor de retorno le dirá la cantidad de espacio que necesita para asignar a su cadena:

int how_much_space = snprintf(NULL, 0, fmt_string, param0, param1, ...); 
+0

Todavía es una mejor idea tratar de generar primero una variable de pila de tamaño fijo, ya que la gran mayoría de las impresiones tendrán un tamaño inferior. Eso significa que la gran mayoría no necesita la impresión/malloc/impresión/gratis, sino solo imprimir. Solo el pequeño número por encima del límite necesita el comportamiento completo. – paxdiablo

+0

@Pax: probablemente sea cierto, pero eso es una optimización del rendimiento. A menudo es apropiado, pero no siempre. Un ejemplo de cuando no lo es: no tienes mucho espacio en la pila, y esperas que la gran mayoría de las impresiones sea más grande que cualquier tamaño de búfer que quieras colocar en la pila. Entonces siempre usas el montón. –

+0

Dado este escenario, ¿por qué no asignar previamente una porción _dedicated_ de la memoria para las impresiones en el montón y beneficiarse de la optimización del rendimiento? – user666412

5

Como otros han mencionado, snprintf() devolverá el número de caracteres necesarios en una memoria intermedia para evitar la salida de truncarse. Simplemente puede llamarlo con un parámetro de longitud de búfer 0 para obtener el tamaño requerido y luego usar un búfer de tamaño adecuado.

Para una ligera mejora en la eficiencia, puede llamarlo con un búfer que sea lo suficientemente grande para el caso normal y solo hacer una segunda llamada al snprintf() si la salida se trunca. Para asegurarme de que los búferes se liberan correctamente en ese caso, a menudo utilizaré un objeto auto_buffer<> que maneja la memoria dinámica para mí (y tiene el búfer predeterminado en la pila para evitar una asignación de montón en el caso normal))

Si está utilizando un compilador de Microsoft, MS tiene un _snprintf() no estándar que tiene serias limitaciones de no siempre la terminación nula del búfer y no indicando cuán grande debe ser el búfer.

Para evitar el no soporte de Microsoft, utilizo a nearly public domain snprintf() from Holger Weiss.

Por supuesto, si su compilador que no es MS C o C++ falta snprintf(), el código del enlace de arriba debería funcionar igual de bien.

0

Dado que está utilizando C++, no hay necesidad de utilizar ninguna versión de sprintf. Lo más simple es usar std :: ostringstream.

std::ostringstream oss; 
oss << a << " " << b << std::endl; 

oss.str() devuelve un std :: string con el contenido de lo que ha escrito a OSS. Use oss.str().c_str() para obtener un const char *. Va a ser mucho más fácil lidiar a largo plazo y elimina las fugas de memoria o los desbordamientos del búfer. En general, si te preocupas por problemas de memoria como los de C++, no estás usando el lenguaje a su máximo potencial, y debes reconsiderar tu diseño.

+0

Una palabra de advertencia: C++ ha incluido una gran cantidad de pequeños extras en las funciones de transmisión, y estos pueden morderlo de manera importante. En particular, las transmisiones admiten configuraciones regionales, que pueden cambiar la forma en que se formatean sus números. Los números que se generaron en una secuencia establecida en una configuración regional no se pueden leer en una secuencia con una configuración regional diferente. Si puede garantizar que no se utilizarán otras configuraciones regionales, entonces está bien. Fuimos mordidos por esto porque estábamos usando una DLL que se conectaba a una aplicación host que usa configuraciones regionales. – AHelps

+0

http://unixhelp.ed.ac.uk/CGI/man-cgi?sprintf+3 ¿Qué fue eso de los locales? –

1

Por lo que vale, asprintf es una extensión de GNU que gestiona esta funcionalidad.Acepta un puntero como un argumento de salida, junto con una cadena de formato y una cantidad variable de argumentos, y escribe de nuevo en el puntero la dirección de un búfer adecuadamente asignado que contiene el resultado.

Se puede utilizar de este modo:

#define _GNU_SOURCE 
#include <stdio.h> 

int main(int argc, char const *argv[]) 
{ 
    char *hi = "hello"; // these could be really long 
    char *everyone = "world"; 
    char *message; 
    asprintf(&message, "%s %s", hi, everyone); 
    puts(message); 
    free(message); 
    return 0; 
} 

Espero que esto ayude a alguien!

Cuestiones relacionadas