2009-10-29 18 views
5
{ 
    char *a, *b; 

    printf("%lx\n",(b-a)); 
} 

Normalmente funciona, de hecho, no me lo imagino dando una advertencia o fallando en una máquina de 32 bits o de 64 bits. ¿Pero es eso lo correcto para ANSI C y el conocimiento del tamaño? Me gustaría que este código funcione en todas las plataformas posibles, incluidos sistemas que no sean Unix y embebidos.char * a, * b; ¿Qué tipo es (b-a) y cómo lo imprimo?

Respuesta

22

b - a es un ptrdiff_t, que puede imprimir con %td en su formato printf. Desde la sección de especificaciones 6.5.6 operadores aditivos:

Cuando se restan dos punteros, tanto deberán apuntar a elementos del mismo objeto de matriz, o uno más allá del último elemento del objeto array; el resultado es la diferencia de los subíndices de los dos elementos de la matriz. El tamaño del resultado está definido por la implementación y su tipo (un tipo de entero con signo) es ptrdiff_t definido en el encabezado <stddef.h>.

Para printf y las funciones relacionadas, sección 7.19.6 Formatted funciones de entrada/salida:

t Especifica que una siguiente d, i, o, u, x, o X especificador de conversión se aplica a un ptrdiff_t o el correspondiente tipo de argumento entero sin signo; o que un siguiente n especificador de conversión se aplica a un puntero a un ptrdiff_t argumento.

me hurgó en la especificación poco más, y parece indicar que una diferencia de dos punteros podría ni siquiera caben en un ptrdiff_t, en el que está definido el comportamiento de caso:

J. 2 Comportamiento indefinido
- El resultado de restar dos punteros no es representable en un objeto del tipo ptrdiff_t (6.5.6).

Aunque no puedo imaginar ninguna implementación donde pueda surgir. Supongo que puede marcar PTRDIFF_MIN y PTRDIFF_MAX en <stdint.h> para estar realmente seguro.

+0

+1 Para las peculiaridades estándar de C, como una diferencia de puntero que no encaja dentro de un 'ptrdiff_t' (también puedo pensar en palabras mucho más desagradables que" caprichos "). –

+1

Sí, resulta que 'PTRDIFF_MIN' /' PTRDIFF_MAX' podría ser tan pequeño como +/- 65536 (según la especificación). Sin embargo, las implementaciones que verifiqué no hicieron cosas locas como esas; en la práctica, parece improbable que surja algo extraño como ese. –

+0

Consideraría una implementación donde 'ptrdiff_t' es más pequeño (ancho en bits) que el tamaño de matriz más grande inusualmente roto, e incluso implementaciones donde el número de * bits de valor * es menor (es decir, arreglos mayores que los elementos' SIZE_MAX/2' permitido) bastante peligroso ... –

2

Puesto que no se ha inicializado las variables un y b, el código da un comportamiento indefinido. Pero aparte de eso, el tipo de b-a es ptrdiff_t, que es lo suficientemente grande como para contener el resultado. Si tiene una C lo suficientemente moderna, puede imprimirla con % tx.

Si no desea utilizar % tx, debe convertir su resultado coincide con lo que en realidad (y no sólo por accidente) el especificador de formato:

printf("%lx", (unsigned long)(a-b)); 

No es inconcebible que una el sistema podría tener, por ejemplo, un espacio de direcciones de 32 bits y un ptrdiff_t de 32 bits, pero una longitud de 64 bits, y luego su error de impresión fallaría.

+3

No, size_t es sin signo. –

+0

@Nikolai: Gracias. Debo estar borracho. –

+0

¿Qué tan moderno debería ser mi C? Mi actual C la tiene, pero ¿cuál es la probabilidad de que golpee una C que no la tiene? ¿Cuándo se agregó% tx a la especificación? –

5

Es ptrdiff_t. De man stddef.h:

 
ptrdiff_t 
       Signed integer type of the result of subtracting two pointers. 

impresión con %td.

12

El resultado de b - a solo se define cuando tanto a como b apuntan a elementos de la misma matriz de caracteres. Este requisito también se puede interpretar como a y b apuntando a bytes que pertenecen al mismo objeto, ya que cada objeto se puede reinterpretar como una matriz de caracteres.

De lo contrario, el resultado no está definido. Es decir. un intento de restar tales punteros da como resultado un comportamiento indefinido.

Cuando se define el resultado, tiene el tipo ptrdiff_t. ptrdiff_t es un nombre typedef y qué tipo se esconde detrás de ese typedef nombre está definido por la implementación. Sin embargo, se sabe que el tipo está firmado.

También tenga en cuenta que el lenguaje C no garantiza que ptrdiff_t sea lo suficientemente grande como para contener el resultado de una resta, incluso si los punteros apuntan a los elementos de la misma matriz. Si los punteros están demasiado separados para el tipo ptrdiff_t para acomodar el resultado, el comportamiento no está definido.

No hay especificador específica printf formato para ptrdiff_t incluso en C99, por lo que probablemente va a estar mejor convirtiéndola en una cantidad suficientemente grande de tipo entero con signo y usar un especificador de formato para ese tipo

printf("%ld\n", (long) (b - a)); 

Corrección : C99 tiene un modificador de longitud para ptrdiff_t. La forma correcta de imprimir el resultado en C99 sería

printf("%td\n", b - a); 

Tenga en cuenta que t es un modificador de longitud. Se puede combinar con d, o, u, x o X especificadores de conversión, dependiendo del formato de salida que desee obtener. En C89/90, aún deberá seguir utilizando un tipo de firma suficientemente grande.

P.S. Dijiste que no puedes imaginar que falle en una máquina de 32 bits o de 64 bits. De hecho, es muy fácil imaginarlo (o hacerlo) fallar. Verá que el ptrdiff_t en una máquina de 32 bits suele ser del tipo de 32 bits. Como es un tipo firmado, solo tiene 31 bits disponibles para representar la magnitud del valor. Si toma dos punteros que están más separados (es decir, requiere 32 bits para representar la "distancia"), el resultado de b - a se desbordará y no tendrá sentido.Para evitar esta falla necesitaría al menos 33-bit firmó ptrdiff_t en una máquina de 32 bits, y al menos firmó 65-bit ptrdiff_t en la máquina de 64 bits. Las implementaciones normalmente no hacen eso, solo usan el "permiso" del estándar para producir un comportamiento indefinido en el desbordamiento.

+0

¿Qué es "suficientemente grande"? Fundir a lo largo parece "suficientemente grande" a primera vista, ¿hay un sistema donde un puntero no cabe en un largo y requiere una larga longitud? Claramente, en los sistemas de 32 bits, un largo es de 32 bits y, por lo tanto, es lo suficientemente grande, y en los sistemas de 64 bits, un largo es de 64 bits y, por lo tanto, es lo suficientemente grande. Pero estoy realmente cubierto para todos los sistemas (¡ja!) Gracias por la advertencia sobre los signos, no estoy muy preocupado de que los dos indicadores en cuestión apunten realmente al mismo conjunto, y no puedo imaginar que ese conjunto sería más grande que la mitad del tamaño del espacio de direcciones. –

+0

"Suficientemente grande" significa: analice su plataforma (el tamaño del puntero, específicamente) y observe los tamaños de los diversos tipos integrales en su plataforma. Elija un tipo integral particular en consecuencia. Normalmente eso sería un tipo integral firmado cuyo tamaño es el mismo que el tamaño del puntero. – AnT

+0

Si la implementación garantiza que los punteros en la misma matriz nunca difieren en más de 2^31-1 o 2_63-1 (es decir, que las matrices nunca tienen más que estos muchos elementos), la posibilidad de desbordamiento no es un problema. Básicamente, toda una implementación debe hacer para asegurar que esto no permita tamaños 'malloc' mayores que' SIZE_MAX/2'. –

Cuestiones relacionadas