2010-12-17 8 views
6

Sin duda, todos los demás estudiantes de C lo han notado; es nuevo para mí¿Las varargs ofrecen un tipo de polimorfismo de pobre?

Si yo anuncio

int xlate(void *, ...); 

y luego definir xlate() de varias maneras diferentes (tal vez todas las definiciones menos uno son #ifdef -ed a cabo):

int xlate (char *arg1) { ... } 

int xlate (int arg1, char *arg2, int arg3) { ... } 

int xlate (char arg1, int *arg2) { ... } 

y omitir cualquier mención de va_list - - nunca mencionarlo - en cada definición de de xlate(); y luego llame a xlate() respetando varias de sus varias definiciones, parece que cada versión compilada de xlate() funciona exactamente como yo quiero, al menos bajo gcc y msvc.

¿Este comportamiento de compilador relajado, poco exigente y generoso está garantizado por C99?

Gracias!

- Pete

+0

Formatee su código la próxima vez. –

Respuesta

3

Además de la respuesta de la CAF:

Esto no puede trabajar debido a varios problemas, y el estándar no se podía hacer otra cosa que prohibir tal cosa:

Prototipos dicen al lado llamante cómo las conversiones de argumentos deben realizarse cuando se llama a la función. Ya el ejemplo que usted da no funcionaría de manera confiable para el primer parámetro. Usted lo declara void* y luego int en otro. Como ambos pueden tener un ancho diferente, su código está condenado a fallar en la mayoría de las arquitecturas de 64 bits.

Peor aún, la notación ... le dice a la parte que llama para aplicar las promociones predeterminadas para los argumentos restantes. Por ejemplo, si su implementación esperaría un float, el lado de la llamada siempre proporcionaría un double y, de nuevo, su código fallaría gravemente (= últimamente).

Entonces, las arquitecturas modernas tienen reglas complicadas que tipo de argumentos ponen en la pila y que se guardan en registros. Esto depende del tipo de argumento, por ejemplo, los enteros y los puntos flotantes tienen diferentes conjuntos de registros. Entonces esto hará que tus argumentos sean completamente incorrectos.

4

No, es más de un hombre pobre sobrecarga. El polimorfismo (la capacidad de realizar una acción en varios tipos de objetos y que cada uno haga lo correcto) en C generalmente se realiza con structures containing function pointers.

Y no puede simplemente ciegamente utilizar tantos argumentos como desee. O tiene un número mínimo fijo de argumentos que pueden informar a la función cuántos variables esperar, o usted tiene un argumento centinela al final para indicar que ha terminado.

En otras palabras, algo así como:

printf ("%d: %s\n", int1, charpointer2); 
x = sum_positive_values (1, 2, 3, 4, 5, -1); 
+0

Hmmm. ¿Número mínimo fijo de args? Eso no me parece bien, dado lo poco que sé sobre las funciones variadas. Pero, sí, debe haber al menos un arg. Número máximo fijo, tal vez. Mira, me preocupa si el ret de la función dejará la pila en buen estado; tu comentario se basa en esa preocupación. –

+0

Me refiero al mínimo fijo como en 'printf' (y el mínimo fijo incluye" uno "pero puede haber más como en' [fs] printf') - necesitas los argumentos fijos para decirte cuántos args más están , ya sea un conteo o una cadena con marcadores '%'. – paxdiablo

+0

Sí, estoy de acuerdo que "uno" es un número :-). Pero, observo, observo experimentalmente que puedo llamar a un func con cualquier cantidad de args y el func se comportará (y volverá) correctamente sin importar cuántos argumentos espere. Solo por diversión, pruebe su ejemplo de printf, ligeramente modificado: printf ("% d:% s% s% s% s% s% s% s% s% s% s% s% s \ n", int1, charpointer2); ¡Funciona genial! –

3

Si se declara xlat como tomar void *, no se puede simplemente ir y ponerlo en práctica con int. Incluso sobrecarga hombre pobre se hace correctamente, y que tal vez parecerse

 
enum { 
     T_FOO, 
     T_BAR, 
}; 

void xlat(enum tp type, ...) 
{ 
     struct foo *foo; 
     struct bar *bar; 
     va_list argp; 
     va_start(argp, type); 
     if (type == T_FOO) { 
       foo = va_arg(argp, struct foo *); 
       do_something_with_foo; 
     } else if (type == T_BAR) { 
       bar = va_arg(argp, struct bar *); 
       do_something_with_bar; 
     } 
}

Aunque supongo que es más como una sobrecarga de polimorfismo.

+0

Bueno, como void * puede tener algún valor, imaginé que la función de recepción (es decir, xlate) puede comprender/usar/interpretar cualquier argumento de la manera que quiera. Ese es todo el punto, en realidad. Gracias por tus comentarios. Espero escuchar que C99 promete tal comportamiento. Sí, es sobrecarga. –

2

No es el compilador lo que hace que este divertido negocio funcione tanto como es el ABI de la plataforma que está compilando. Una llamada de función variadica se realiza usando las mismas reglas que cualquier otra llamada de función, por lo que es lógico que si pasa el número y tipo de argumentos correctos, incluso si la función de llamada cree que es variadic, la función de recepción podrá entender los argumentos correctamente (incluso si la función de recepción no es variadica).

+0

Sí, eso es correcto. Bien puesto: el que llama piensa que la función es variada, pero a la función no le importan nada esas suposiciones ingenuas. Y sí, es razonable. –

+1

Una llamada de función variadica * no * necesariamente se realiza usando las mismas reglas que cualquier otra llamada de función, particularmente en plataformas donde se espera que el destinatario muestre los argumentos de función de la pila. El estándar C tiene algunos problemas para permitir a los implementadores esa latitud. – caf

3

No, tal comportamiento es no garantizado por la norma. El texto relevante es en §6.5.2.2:

9 Si la función se define con un tipo que no es compatible con el tipo (de la expresión) a la que apunta la expresión que indica la llama función, el comportamiento no está definido.

Algunas plataformas necesitan utilizar una convención de llamada diferente al llamar a las funciones varargs, porque su convención de llamadas habitual requiere que el destinatario sepa cuántos argumentos reales se pasaron. El estándar C se escribió con esto específicamente en mente, por lo que las funciones varargs solo pueden invocarse a través de una declaración varargs correctamente tipada, y las expresiones que denotan funciones varargs solo se pueden usar para llamar a las funciones varargs.

Puede hacer lo que desee creando declaraciones coincidentes de cada función, envuelto en la misma magia #ifdef que selecciona una que también se utiliza para seleccionar la definición de función correcta.

+0

Lo siento, debo ser denso, pero no veo qué tiene que ver el párrafo 9 con los argumentos de la función. Mientras leo el párrafo, me dice que si llamo a una función esperando que sea de un tipo (int, por ejemplo) y en realidad es de otro tipo (char *, por ejemplo), cuando llamo a esa función "la comportamiento "no está definido. Tiene mucho sentido. Pero, ¿qué dice el párrafo sobre los argumentos de función? ¿Cómo estoy malinterpretando el párrafo 9, por favor? ¡Gracias! –

+1

@Pete Wilson: el "tipo" de la función incluye su tipo de devolución * y * sus argumentos. Por ejemplo, el tipo de la función 'strcmp()' es 'int (const char *, const char *)' – caf

+0

Sigh. Usted sabe mucho mejor que eso. La masa de cosas que mencionas podría ser la "firma" de strcmp(), pero bien sabes que no es el "tipo" de strcmp() más que el contenido de una variable, o la longitud de la variable en bits, dice algo sobre su "tipo". El "tipo" de una función es exactamente el tipo de valor que devuelve. Período. El "tipo" de strcmp() es int. Período. La noción del "tipo" de una función tal como la estamos hablando ha existido y se ha entendido desde los primeros días de Fortran en la década de 1950, si no antes. De nuevo, esto no es una novedad para ti, pero no puedo dejar de soportar lo que dijiste. –