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 unchar
fuera de la lista de argumentos, tienes que pasarint
ava_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 hacerc
achar
pero desdeputc()
toma unint
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 tipoint
que se convierte deint
achar
por la inicialización dech
. si clarochar
se firma y el resultado superaCHAR_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. Sich
luego se pasa aputc
esa función en sí misma lo convertirá enunsigned 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 unchar
yputc
toma unint
argumento, también podrías usarint
consecuentemente. (Si la persona que llama dewriteFile
sucede que pasa un argumento que está fuera del rango deunsigned char
será convertido porputc
que normalmente debería darle el comportamiento esperado).–Keith Thompson
21 de enero de 2015 a las 18:22
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 int
y 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 int
o 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 . La regla se establece en la sección sobre llamadas a funciones, N1570 6.5.2.2 párrafo 7:<stdarg.h>
no menciona la promoción del argumento
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 int
o 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 int
por 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