2011-11-10 15 views
6

tengo una estructura para representar cadenas en memoria en busca de esta manera:Usando size_t para especificar la precisión de una cadena en printf de C

typedef struct { 
    size_t l; 
    char *s; 
} str_t; 

Creo con size_t tiene sentido para especificar la longitud de una cadena de carbón . También me gustaría imprimir esta cadena usando printf("%.*s\n", str.l, str.s). Sin embargo, la precisión * espera un argumento int, no size_t. No he podido encontrar nada relevante sobre esto. ¿Hay alguna manera de utilizar esta estructura correctamente, sin un molde a int en la llamada printf()?

+0

¿no tiene previsto anular la terminación del 'char *'? Si es nulo terminado, no veo qué ajuste la precisión a la longitud lo conseguiría en primer lugar. Más allá de eso, creo que tus elecciones son: lanzar o cambiar tu 'size_t' por' int' y nunca permitir que sea negativo. –

+2

@EvanTeran Las cadenas realmente apuntan a las piezas dentro de un buffer más grande, por lo que el carácter 'null' está en algún otro lugar. El tamaño de un 'int' no es un problema, ya que las cadenas son generalmente de unos pocos cientos de bytes. Lo que me frustra es que a pesar de que 'size_t' encajaría muy bien en este propósito, C99 no define un modificador de precisión especial que acepte un argumento' size_t'. –

Respuesta

3

Se podría hacer una macro

#define STR2(STR) (int const){ (STR).l }, (char const*const){ (STR).s } 

y luego usar esto como printf("%.*s\n", STR2(str)).

Tenga en cuenta que esto evalúa STR dos veces, así que tenga cuidado con los efectos secundarios, pero probablemente ya lo sabía.

Editar:

estoy usando inicializadores compuestos de tal manera que éstas son las conversiones implícitas. Si las cosas van mal, hay más posibilidades de que el compilador te advierta que con un lanzamiento explícito.

por ejemplo, si tiene un campo STR.l que es un puntero y sólo se había puesto un yeso para int, todos los compiladores podría convertir felizmente ese puntero a int. Similar para el campo .s esto realmente tiene que corresponder a un char* o algo compatible, de lo contrario, vería una advertencia o un error.

+0

Todavía se lanza, pero se ve mejor que el lanzamiento en cada paso. Me pregunto, ¿cómo es exactamente ese inicializador compuesto diferente de un elenco explícito? Los documentos de GCC mencionan que: "Literales compuestos para tipos escalares y tipos de unión también están permitidos, pero el compuesto literal es equivalente a un reparto". (http://gcc.gnu.org/onlinedocs/gcc/Compound-Literals.html) –

+0

@LuciStanescu, no es un elenco, como dije, una conversión implícita. Todos los lanzamientos son conversiones, pero no todas las conversiones son lanzados. La diferencia sería si pasaras un '" STR "' que tiene un campo '.l' que sería un puntero y no un tipo entero. Si fuera un cast, todos los compiladores se convertirían felizmente. Para una conversión, la mayoría de los compiladores lo advertirían. Por cierto, lo mismo es cierto para el campo '.s', voy a editar mi publicación. –

+0

De hecho, aunque el manual de GCC menciona que para los tipos escalares el inicializador compuesto es equivalente a la conversión, sí se queja si se pasa un tipo no interger. –

5
printf("%.*s\n", (int)str.l, str.s) 
//    ^^^^^ use a type cast 

Editar

bien, no he leído la pregunta correctamente. No querrás usar un tipo de elenco, pero creo que, en este caso: difícil.

O eso, o simplemente usar fwrite

fwrite(str.s, str.l, 1, stdout); 
printf("\n"); 
+1

+1 para la sugerencia 'fwrite'. Aunque probablemente iría con 'fwrite (str.s, 1, str.l, stdout)' y verificaría el valor de retorno para ver cuántos caracteres se escribieron correctamente. – Nemo

+0

El ejemplo de la llamada 'printf()' que di fue en realidad solo un ejemplo y la pregunta claramente especifica que el objetivo es usar size_t para especificar una precisión. Si te estás preguntando por qué no me gusta la sugerencia 'fwrite': una vez que comienzas a tener cadenas constantes más complejas, str_t strings múltiples, números y usas' snprintf' para construir un mensaje, se vuelve demasiado molesto no usar el formato funciones. Así que creo que estoy atascado con la sugerencia "dura" de tu respuesta :-). Pero ¡salud! –

+0

@nemo: pensé en eso, pero luego tienes que lidiar con un conteo de retorno corto> 0, mientras que en mi versión, el valor de retorno es 1 (éxito) o 0 (error). De acuerdo con los documentos de 'fwrite()' en mi PC (Mac OS X), de todos modos obtiene un conteo corto de un error de escritura, por lo que probablemente quiera parar, no importa qué ocurra en ese momento. – JeremyP

1

No hay garantía de que el size_t es un int, o que pueda ser representado dentro de un int. Es solo parte del legado de C no definir el tamaño exacto de un int, junto con las preocupaciones de que la implementación de size_t deba ser aprovechada para abordar áreas de memoria grandes (aquellas que tienen más de MAX_INT valores en ellas).

El error más común relacionado con size_t es suponer que es equivalente a unsigned int. Esos bugs viejos eran comunes, y por experiencia personal hace que la migración de una arquitectura de 32 bits a una arquitectura de 64 bits sea un problema, ya que necesita deshacer esta suposición.

En el mejor de los casos, puede usar un modelo. Si realmente quieres deshacerte del elenco, puedes descartar alternativamente el uso de size_t.

+1

En realidad, hay una garantía de que 'size_t' nunca será equivalente a' int'; sin embargo, podría ser equivalente a 'unsigned int'. (No debe estar firmado, su tamaño puede ser o no el mismo que el tamaño de 'int'.) –

Cuestiones relacionadas