tipo de caracteres en va_arg

7 minutos de lectura

Tengo la siguiente función que escribe argumentos pasados ​​​​a un archivo binario.

void writeFile(FILE *fp, const int numOfChars, ...)
{
   va_list ap;
   va_start(ap, numOfChars);
   for(int i = 0; i < numOfChars; i++)
   {
      const char c = va_arg(ap, char);
      putc(c, fp);
   }
   va_end(ap);
}

Al compilar, recibo la siguiente advertencia del compilador

 warning: second argument to 'va_arg' is of promotable type 'char'; this va_arg
      has undefined behavior because arguments will be promoted to 'int' [-    Wvarargs]

Ahora, según tengo entendido, C quiere promover el tipo char a int. ¿Por qué C quiere hacer esto? En segundo lugar, ¿es la mejor solución volver a convertir el int en un char?

Ahora, según tengo entendido, C quiere promover el tipo char a int. ¿Por qué C quiere hacer esto?

Porque eso es lo que dice la norma. Si pasa un valor integral con un rango de conversión más pequeño que el de int (p.ej char, bool o short) a una función que toma un número variable de argumentos, se convertirá a int. Presumiblemente, la razón de esto tiene sus raíces en el rendimiento, donde era (y de hecho, a menudo todavía lo es hoy en día) mejor pasar valores alineados con un límite de palabra de máquina.

En segundo lugar, ¿es la mejor solución volver a convertir el int en un char?

Sí, pero ni siquiera necesitas un molde, una conversión implícita servirá:

char ch = va_arg(ap, int);

  • No se necesita ni un molde ni una conversión implícita. Vea el final de mi respuesta (actualizada).

    –Keith Thompson

    21 de enero de 2015 a las 15:55

  • @KeithThompson ¿Qué quiere decir con que no se necesita una conversión implícita? Seguro que no puedes escribir char ch = va_arg(ap, char) ya que es indefinido? si quieres un char fuera de la lista de argumentos, tienes que pasar int a va_arg¿no es así?

    – El Croissant Paramagnético

    21 de enero de 2015 a las 18:05

  • Lo que quiero decir es que el código correcto sería int c = va_arg(ap, int); putc(c, fp);. Tú pudo hacer c a charpero desde putc() toma un int argumento no tiene mucho sentido hacerlo.

    –Keith Thompson

    21 de enero de 2015 a las 18:13


  • No es incorrecto (aunque podría tener algunos problemas). va_arg(ap, int) devuelve un resultado de tipo intque se convierte de int a char por la inicialización de ch. si claro char se firma y el resultado supera CHAR_MAX (típicamente 127), entonces el resultado de la conversión está definido por la implementación, aunque casi siempre se someterá al ajuste habitual de complemento a 2. Si ch luego se pasa a putcesa función en sí misma lo convertirá en unsigned char; el resultado de esa conversión está bien definido.

    –Keith Thompson

    21 de enero de 2015 a las 18:21

  • Pero desde va_arg() no puede producir un chary putc toma un int argumento, también podrías usar int consecuentemente. (Si la persona que llama de writeFile sucede que pasa un argumento que está fuera del rango de unsigned charserá convertido por putcque normalmente debería darle el comportamiento esperado).

    –Keith Thompson

    21 de enero de 2015 a las 18:22

avatar de usuario
keith thompson

Las funciones variádicas se tratan de forma especial.

Para una función no variádica, el prototipo (declaración) especifica los tipos de todos los parámetros. Los parámetros pueden ser de cualquier tipo (sin matriz, sin función), incluidos los tipos más estrechos que int.

Para una función variádica, el compilador no conoce los tipos de los parámetros correspondientes a la , .... Por razones históricas y para facilitar el trabajo del compilador, los argumentos correspondientes de tipos más estrechos que int son promovidos a int o para unsigned inty cualquier argumento de tipo float son promovidos a double. (Esta es la razón por printf utiliza los mismos especificadores de formato para float o double argumentos.)

Entonces una función variádica no puedo recibir argumentos de tipo char. Puede llamar a una función de este tipo con un char argumento, pero será promovido a int.

(En las primeras versiones de C, antes de que se introdujeran los prototipos, todos funciones se comportaron de esta manera. Incluso C11 permite declaraciones que no son prototipos, en las que se promueven argumentos estrechos para int, unsigned into double. Pero dada la existencia de prototipos, realmente no hay razón para escribir código que dependa de dichas promociones, excepto en el caso especial de las funciones variádicas).

Por eso, no tiene sentido tener va_arg() aceptar char como argumento de tipo.

Pero el lenguaje no prohibir tal invocación de va_arg(); de hecho, la sección de la norma que describe <stdarg.h> no menciona la promoción del argumento. La regla se establece en la sección sobre llamadas a funciones, N1570 6.5.2.2 párrafo 7:

Si la expresión que denota la función llamada tiene un tipo que incluye un prototipo, los argumentos se convierten implícitamente, como por asignación, a los tipos de los parámetros correspondientes, tomando el tipo de cada parámetro como la versión no calificada de su declarado. escribe. La notación de puntos suspensivos en un declarador de prototipo de función hace que la conversión de tipo de argumento se detenga después del último parámetro declarado. Las promociones de argumento predeterminadas se realizan en argumentos finales.

Y la descripción de la va_arg() macro, 7.16.1.1, dice (énfasis añadido):

Si no hay un siguiente argumento real, o si el tipo no es compatible con el tipo del siguiente argumento real (como promocionado según el argumento por defecto promociones), el comportamiento es indefinido, excepto en los siguientes casos:
[SNIP]

Las “promociones de argumentos predeterminados” convierten los argumentos estrechos en int, unsigned into double. (Un argumento de tipo entero sin signo cuyo valor máximo excede INT_MAX será promovido a unsigned int. Es teóricamente posible para char comportarse de esta manera, pero solo en una implementación muy inusual).

En segundo lugar, ¿es la mejor solución volver a convertir el int en un char?

No, no en este caso. Los yesos rara vez son necesarios; en la mayoría de los casos, las conversiones implícitas pueden hacer el mismo trabajo. En este caso particular:

const char c = va_arg(ap, char);
putc(c, fp);

el primer argumento para putc ya es de tipo intpor lo que esto se escribe mejor como:

const int c = va_arg(ap, int);
putc(c, fp);

los int el valor se convierte por putc a unsigned char y escrito a fp.

  • Gracias Keith. Sigo aprendiendo cosas nuevas sobre C todos los días. 🙂

    – Un hombre

    20 de enero de 2015 a las 20:25

  • “Pero el lenguaje no prohibir […]” — Estoy perdido aquí. Está explícitamente indefinido: 7.16.1.1 (va_arg) p2 “[I]F escribe no es compatible con el tipo del siguiente argumento real (como se promociona de acuerdo con las promociones de argumento predeterminadas), el comportamiento no está definido, excepto en los siguientes casos: […]”

    – mafso

    21 de enero de 2015 a las 11:04

  • @mafso: Comportamiento indefinido no significa que esté prohibido. va_arg(ap, char) no es un error de sintaxis ni una violación de restricción; no se requiere un compilador conforme para rechazarlo o diagnosticarlo.

    –Keith Thompson

    21 de enero de 2015 a las 15:49

  • Estoy de acuerdo (ni siquiera estoy seguro de si un compilador puede rechazar un programa con un (no alcanzado) va_arg(ap, char), GCC advierte que el programa se cancelará si se alcanza el código), estaba confundido acerca de “no menciona la promoción de argumentos”. De todos modos, considero útil esta respuesta con o sin esa parte, incluso si la encuentro un poco engañosa. (Leería “prohibir” como “prohibir el código estrictamente conforme”, pero tal vez solo soy yo).

    – mafso

    21 de enero de 2015 a las 16:05

  • @mafso: Probablemente estaba confundido acerca de “no menciona la promoción de argumentos” porque estaba mal. Lo acabo de actualizar.

    –Keith Thompson

    13 de junio de 2019 a las 21:36

¿Ha sido útil esta solución?