printf - fuente de errores? [cerrado]

9

Estoy usando mucho printfpara fines de rastreo / registro en mi código, descubrí que es una fuente de error de programación. Siempre encontré que el operador de inserción ( <<) es algo extraño, pero estoy empezando a pensar que al usarlo podría evitar algunos de estos errores.

¿Alguien alguna vez tuvo una revelación similar o solo estoy agarrando pajillas aquí?

Algunos quitan puntos

  • Mi línea de pensamiento actual es que la seguridad de tipografía supera cualquier beneficio de usar printf. El verdadero problema es la cadena de formato y el uso de funciones variadas que no son de tipo seguro.
  • Tal vez no <<usaré las variantes de flujo de salida stl, pero ciertamente buscaré usar un mecanismo de tipo seguro que sea muy similar.
  • Gran parte del rastreo / registro es condicional, pero me gustaría ejecutar siempre el código para no perder errores en las pruebas solo porque es una rama raramente tomada.
John Leidegren
fuente
44
printfen el mundo de C ++? Me estoy perdiendo algo aquí?
user827992
10
@ user827992: ¿Echas de menos el hecho de que el estándar C ++ incluye la biblioteca del estándar C por referencia? Es perfectamente legal usarlo printfen C ++. (Si es una buena idea es otra pregunta).
Keith Thompson
2
@ user827992: printftiene algunas ventajas; mira mi respuesta
Keith Thompson
1
Esta pregunta es bastante limítrofe. Las preguntas de "¿Qué piensan ustedes?" A menudo están cerradas.
dbracey
1
@vitaut, supongo (gracias por el consejo). Estoy un poco perplejo por la moderación agresiva. Realmente no fomenta discusiones interesantes sobre situaciones de programación, que es de lo que me gustaría tener más.
John Leidegren

Respuestas:

2

printf, particularmente en los casos en los que podría interesarle el rendimiento (como sprintf y fprintf) es un truco realmente extraño. Constantemente me sorprende que las personas que trabajan con C ++ debido a una sobrecarga de rendimiento minúsculo relacionada con las funciones virtuales continúen defendiendo el io de C.

Sí, para descubrir el formato de nuestra salida, algo que podemos saber al 100% en el momento de la compilación, analicemos una cadena de formato en el tiempo de ejecución dentro de una tabla de salto masivamente extraña usando códigos de formato inescrutables.

Por supuesto, estos códigos de formato no se pueden hacer para que coincidan con los tipos que representan, eso sería demasiado fácil ... y cada vez que se busca se le recuerda si es% llg o% lg que este lenguaje (fuertemente tipado) lo hace descifre los tipos manualmente para imprimir / escanear algo, Y fue diseñado para procesadores anteriores a 32 bits.

Admito que el manejo del ancho y la precisión del formato de C ++ es voluminoso y podría usar algo de azúcar sintáctico, pero eso no significa que tenga que defender el extraño truco que es el principal sistema io de C. Los conceptos básicos absolutos son bastante fáciles en cualquier idioma (aunque probablemente debería estar usando algo como una función de error personalizada / flujo de error para el código de depuración de todos modos), los casos moderados son como expresiones regulares en C (fácil de escribir, difícil de analizar / depurar) ), y los casos complejos imposibles en C.

(Si utiliza los contenedores estándar, escriba algunas sobrecargas rápidas del operador con plantilla << que le permitan hacer cosas como la std::cout << my_list << "\n";depuración, donde my_list es de tipo list<vector<pair<int,string> > >).

jkerian
fuente
1
El problema de la biblioteca estándar de C ++ es que la mayoría de las encarnaciones se implementan operator<<(ostream&, T)llamando ... bueno sprintf,! El rendimiento de sprintfno es óptimo, pero debido a esto, el rendimiento de iostreams es generalmente aún peor.
Jan Hudec
@ JanHudec: Eso no ha sido cierto durante aproximadamente una década en este momento. La impresión real se realiza con las mismas llamadas de sistema subyacentes, y las implementaciones de C ++ a menudo llaman a las bibliotecas de C para eso ... pero eso no es lo mismo que enrutar std :: cout a través de printf.
jkerian
16

Mezclar salidas de estilo C printf()(o puts()o putchar()o ...) con std::cout << ...salidas de estilo C ++ puede no ser seguro. Si recuerdo correctamente, pueden tener mecanismos de almacenamiento en búfer separados, por lo que la salida podría no aparecer en el orden previsto. (Como AProgrammer menciona en un comentario, sync_with_stdioaborda esto).

printf()es fundamentalmente de tipo inseguro. El tipo esperado para un argumento está determinado por la cadena de formato ( "%d"requiere un into algo que promueva int, "%s"requiere un char*que debe apuntar a una cadena de estilo C terminada correctamente, etc.), pero pasar el tipo de argumento incorrecto da como resultado un comportamiento indefinido , no es un error diagnosticable. Algunos compiladores, como gcc, hacen un trabajo razonablemente bueno de advertencia sobre las discrepancias de tipo, pero solo pueden hacerlo si la cadena de formato es literal o se conoce de otro modo en el momento de la compilación (que es el caso más común), y tal el idioma no requiere advertencias. Si pasa el tipo de argumento incorrecto, pueden suceder cosas arbitrariamente malas.

El flujo de E / S de C ++, por otro lado, es mucho más seguro, ya que el <<operador está sobrecargado para muchos tipos diferentes. std::cout << xno tiene que especificar el tipo de x; el compilador generará el código correcto para cualquier tipo que xtenga.

Por otro lado, printflas opciones de formato de IMHO son mucho más convenientes. Si quiero imprimir un valor de punto flotante con 3 dígitos después del punto decimal, puedo usarlo "%.3f", y no tiene ningún efecto en otros argumentos, incluso dentro de la misma printfllamada. C ++ setprecision, por otro lado, afecta el estado de la secuencia y puede estropear la salida posterior si no tiene mucho cuidado de restaurar la secuencia a su estado anterior. (Este es mi motivo personal favorito; si me falta alguna forma limpia de evitarlo, comente).

Ambos tienen ventajas y desventajas. La disponibilidad de printfes particularmente útil si tiene un fondo C y está más familiarizado con él, o si está importando código fuente C en un programa C ++. std::cout << ...es más idiomático para C ++, y no requiere tanto cuidado para evitar desajustes de tipo. Ambos son C ++ válidos (el estándar C ++ incluye la mayoría de la biblioteca estándar C por referencia).

Es probablemente la mejor manera de utilizar std::cout << ...por el bien de otros programadores de C ++ que pueden trabajar en su código, pero se puede utilizar cualquiera de ellos - en especial en el código de seguimiento que se va a tirar a la basura.

Y, por supuesto, vale la pena pasar un tiempo aprendiendo cómo usar depuradores (pero eso podría no ser factible en algunos entornos).

Keith Thompson
fuente
No se menciona la mezcla en la pregunta original.
dbracey
1
@dbracey: No, pero pensé que valía la pena mencionarlo como un posible inconveniente de printf.
Keith Thompson
66
Para el problema de sincronización, vea std::ios_base::sync_with_stdio.
Programador
1
+1 Usar std :: cout para imprimir información de depuración en una aplicación multiproceso es 100% inútil. Al menos con printf, es poco probable que las cosas se intercalen y no puedan ser interpretadas por el hombre o la máquina.
James
@ James: ¿Eso se debe a que std::coututiliza una llamada separada para cada elemento que se imprime? Podría solucionarlo mediante la recopilación de una línea de salida en una cadena antes de imprimirla. Y, por supuesto, también puede imprimir un elemento a la vez con printf; es más conveniente imprimir una línea (o más) en una llamada.
Keith Thompson
2

Lo más probable es que su problema provenga de la combinación de dos gestores de salida estándar muy diferentes, cada uno de los cuales tiene su propia agenda para ese pobre STDOUT. No obtiene garantías acerca de cómo se implementan, y es perfectamente posible que establezcan opciones de descriptor de archivo en conflicto, ambos intentan hacer cosas diferentes, etc. Además, los operadores de inserción tienen una ventaja importante printf: printfle permitirá hacer esto:

printf("%d", SomeObject);

Mientras <<que no lo hará.

Nota: Para la depuración, no usa printfo cout. Usas fprintf(stderr, ...)y cerr.

Linuxios
fuente
No se menciona la mezcla en la pregunta original.
dbracey
Por supuesto, puede imprimir la dirección de un objeto, pero la gran diferencia es que printfno es de tipo seguro y mi línea de pensamiento actual es que la seguridad de tipo supera cualquier beneficio de usar printf. El problema es realmente la cadena de formato y la función variadic no segura de tipo.
John Leidegren
@ JohnLeidegren: ¿Pero qué SomeObjectpasa si no es un puntero? Obtendrá datos binarios arbitrarios que el compilador decide representar SomeObject.
Linuxios
Creo que leí tu respuesta al revés ... nvm.
John Leidegren
1

Hay muchos grupos, por ejemplo google, a los que no les gustan las transmisiones.

http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Streams

(Abre el triángulo para que puedas ver la discusión). Creo que la guía de estilo de google C ++ tiene MUCHOS consejos muy sensatos.

Creo que la compensación es que las transmisiones son más seguras, pero printf es más claro de leer (y más fácil de obtener exactamente el formato que desea).

dbracey
fuente
2
La guía de estilo de Google es agradable, PERO contiene bastantes elementos que no son adecuados para una guía de uso general . (lo cual está bien, porque después de todo es la guía de Google para el código que se ejecuta en / para Google.)
Martin Ba
1

printfpuede causar errores debido a la falta de seguridad de tipo. Hay algunas formas de abordar que, sin cambiar a iostream's <<operador y más complicado el formato:

  • Algunos compiladores (como GCC y Clang) pueden verificar opcionalmente sus printfcadenas de formato con los printfargumentos y pueden mostrar advertencias como las siguientes si no coinciden.
    advertencia: la conversión especifica el tipo 'int' pero el argumento tiene el tipo 'char *'
  • El script typesafeprintf puede preprocesar sus printfllamadas de estilo para que sean seguras.
  • Las bibliotecas como Boost.Format y FastFormat le permiten usar printfcadenas de formato similares (Boost.Format, en particular, son casi idénticas a printf) manteniendo iostreamsla seguridad de tipo y la extensibilidad de tipo.
Josh Kelley
fuente
1

La sintaxis de Printf es básicamente buena, menos algunos de tipeo oscuro. Si crees que está mal, ¿por qué C #, Python y otros lenguajes usan una construcción muy similar? El problema en C o C ++: no es parte de un lenguaje y, por lo tanto, el compilador no verifica la sintaxis correcta (*) y no se descompone en una serie de llamadas nativas si se optimiza la velocidad. Tenga en cuenta que si optimiza el tamaño, ¡las llamadas printf podrían resultar más eficientes! La sintaxis de transmisión de C ++ no es buena en mi humilde opinión. Funciona, la seguridad de tipos está ahí, pero la sintaxis detallada ... bleh. Quiero decir que lo uso, pero sin alegría.

(*) algunos compiladores HACEN esta comprobación además de casi todas las herramientas de análisis estático (uso Lint y nunca tuve ningún problema con printf desde entonces).

Mar
fuente
1
Existe Boost.Format que combina la conveniente sintaxis ( format("fmt") % arg1 % arg2 ...;) con la seguridad de tipos. A costa de un mayor rendimiento, ya que genera llamadas de secuencia de cadena que generan internamente llamadas sprintf en muchas implementaciones.
Jan Hudec
0

printfes, en mi opinión, una herramienta de salida mucho más flexible para tratar con variables que cualquiera de las salidas de flujo de CPP. Por ejemplo:

printf ( "%d in ANSI = %c\n", j, j ); /* Perfectly valid... if a char ISN'T printing right, I'd just check the integer value to make sure it was okay. */

Sin embargo, es posible que desee utilizar el <<operador CPP cuando lo sobrecarga para un método en particular ... por ejemplo, para obtener un volcado de un objeto que contiene los datos de una persona en particular, PersonData...

ostream &operator<<(ostream &stream, PersonData obj)
{
 stream << "\nName: " << name << endl;
 stream << " Number: " << phoneNumber << endl;
 stream << " Age: " << age << endl;
 return stream;
}

Para eso, sería mucho más efectivo decir (suponiendo que asea ​​un objeto de PersonData)

std::cout << a;

que:

printf ( "Name: %s\n Number: %s\n Age: %d\n", a.name, a.number, a.age );

El primero está mucho más en línea con el principio de encapsulación (No es necesario conocer los detalles, las variables de miembros privados), y también es más fácil de leer.

Aviator45003
fuente
0

Se supone que no debes usar printfen C ++. Siempre. La razón es, como notó correctamente, que es una fuente de errores y el hecho de que imprimir tipos personalizados, y en C ++ casi todo debería ser tipos personalizados, es doloroso. La solución de C ++ son las corrientes.

Sin embargo, hay un problema crítico que hace que las transmisiones no sean adecuadas para ninguna salida visible para el usuario El problema son las traducciones. Un ejemplo de préstamo del manual gettext dice que quieres escribir:

cout << "String '" << str << "' has " << str.size() << " characters\n";

Ahora viene el traductor al alemán y dice: Ok, en alemán, el mensaje debería ser

n Zeichen lang ist die Zeichenkette ' s '

Y ahora estás en problemas, porque él necesita mezclar las piezas. Hay que decir que incluso muchas implementaciones printftienen problemas con esto. A menos que admitan la extensión para que pueda usar

printf("%2$d Zeichen lang ist die Zeichenkette '%1$s'", ...);

Los Boost.Format compatible con los formatos de estilo printf y tiene esta característica. Entonces escribes:

cout << format("String '%1' has %2 characters\n") % str % str.size();

Desafortunadamente, conlleva un poco de penalización de rendimiento, porque internamente crea un flujo de cadena y utiliza el <<operador para formatear cada bit y, en muchas implementaciones, el <<operador llama internamente sprintf. Sospecho que sería posible una implementación más eficiente si realmente se desea.

Jan Hudec
fuente
-1

Está haciendo un gran trabajo inútil, además del hecho de que stlsea ​​malo o no, depure su código con una serie de printfsolo agregar 1 nivel más de posibles fallas.

Simplemente use un depurador y lea algo sobre Excepciones y cómo atraparlas y lanzarlas; trate de no ser más detallado de lo que realmente necesita ser.

PD

printf se usa en C, para el C ++ que tienes std::cout

usuario827992
fuente
No utiliza el rastreo / registro en lugar de un depurador.
John Leidegren