Me gustaría preparar una pequeña herramienta educativa para SO que debería ayudar a los programadores principiantes (e intermedios) a reconocer y desafiar sus suposiciones injustificadas en C, C ++ y sus plataformas.
Ejemplos:
- "enteros envueltos"
- "todos tienen ASCII"
- "Puedo almacenar un puntero de función en un vacío *"
Pensé que un pequeño programa de prueba podría ejecutarse en varias plataformas, que ejecuta las suposiciones "plausibles" que, por nuestra experiencia en SO, generalmente son hechas por muchos desarrolladores principales inexpertos / semiexperimentados y registran las formas en que se rompen en diversas máquinas.
El objetivo de esto no es demostrar que es "seguro" hacer algo (lo que sería imposible de hacer, las pruebas prueban solo cualquier cosa si se rompen), sino demostrar al individuo más incomprensible cómo la expresión más discreta interrumpir en una máquina diferente, si tiene un comportamiento indefinido o definido de implementación. .
Para lograr esto, me gustaría preguntarte:
- ¿Cómo se puede mejorar esta idea?
- ¿Qué pruebas serían buenas y cómo deberían verse?
- ¿Ejecutaría las pruebas en las plataformas en las que puede tener acceso y publicará los resultados, para que terminemos con una base de datos de plataformas, en qué se diferencian y por qué se permite esta diferencia?
Aquí está la versión actual para el juguete de prueba:
#include <stdio.h>
#include <limits.h>
#include <stdlib.h>
#include <stddef.h>
int count=0;
int total=0;
void expect(const char *info, const char *expr)
{
printf("..%s\n but '%s' is false.\n",info,expr);
fflush(stdout);
count++;
}
#define EXPECT(INFO,EXPR) if (total++,!(EXPR)) expect(INFO,#EXPR)
/* stack check..How can I do this better? */
ptrdiff_t check_grow(int k, int *p)
{
if (p==0) p=&k;
if (k==0) return &k-p;
else return check_grow(k-1,p);
}
#define BITS_PER_INT (sizeof(int)*CHAR_BIT)
int bits_per_int=BITS_PER_INT;
int int_max=INT_MAX;
int int_min=INT_MIN;
/* for 21 - left to right */
int ltr_result=0;
unsigned ltr_fun(int k)
{
ltr_result=ltr_result*10+k;
return 1;
}
int main()
{
printf("We like to think that:\n");
/* characters */
EXPECT("00 we have ASCII",('A'==65));
EXPECT("01 A-Z is in a block",('Z'-'A')+1==26);
EXPECT("02 big letters come before small letters",('A'<'a'));
EXPECT("03 a char is 8 bits",CHAR_BIT==8);
EXPECT("04 a char is signed",CHAR_MIN==SCHAR_MIN);
/* integers */
EXPECT("05 int has the size of pointers",sizeof(int)==sizeof(void*));
/* not true for Windows-64 */
EXPECT("05a long has at least the size of pointers",sizeof(long)>=sizeof(void*));
EXPECT("06 integers are 2-complement and wrap around",(int_max+1)==(int_min));
EXPECT("07 integers are 2-complement and *always* wrap around",(INT_MAX+1)==(INT_MIN));
EXPECT("08 overshifting is okay",(1<<bits_per_int)==0);
EXPECT("09 overshifting is *always* okay",(1<<BITS_PER_INT)==0);
{
int t;
EXPECT("09a minus shifts backwards",(t=-1,(15<<t)==7));
}
/* pointers */
/* Suggested by jalf */
EXPECT("10 void* can store function pointers",sizeof(void*)>=sizeof(void(*)()));
/* execution */
EXPECT("11 Detecting how the stack grows is easy",check_grow(5,0)!=0);
EXPECT("12 the stack grows downwards",check_grow(5,0)<0);
{
int t;
/* suggested by jk */
EXPECT("13 The smallest bits always come first",(t=0x1234,0x34==*(char*)&t));
}
{
/* Suggested by S.Lott */
int a[2]={0,0};
int i=0;
EXPECT("14 i++ is strictly left to right",(i=0,a[i++]=i,a[0]==1));
}
{
struct {
char c;
int i;
} char_int;
EXPECT("15 structs are packed",sizeof(char_int)==(sizeof(char)+sizeof(int)));
}
{
EXPECT("16 malloc()=NULL means out of memory",(malloc(0)!=NULL));
}
/* suggested by David Thornley */
EXPECT("17 size_t is unsigned int",sizeof(size_t)==sizeof(unsigned int));
/* this is true for C99, but not for C90. */
EXPECT("18 a%b has the same sign as a",((-10%3)==-1) && ((10%-3)==1));
/* suggested by nos */
EXPECT("19-1 char<short",sizeof(char)<sizeof(short));
EXPECT("19-2 short<int",sizeof(short)<sizeof(int));
EXPECT("19-3 int<long",sizeof(int)<sizeof(long));
EXPECT("20 ptrdiff_t and size_t have the same size",(sizeof(ptrdiff_t)==sizeof(size_t)));
#if 0
{
/* suggested by R. */
/* this crashed on TC 3.0++, compact. */
char buf[10];
EXPECT("21 You can use snprintf to append a string",
(snprintf(buf,10,"OK"),snprintf(buf,10,"%s!!",buf),strcmp(buf,"OK!!")==0));
}
#endif
EXPECT("21 Evaluation is left to right",
(ltr_fun(1)*ltr_fun(2)*ltr_fun(3)*ltr_fun(4),ltr_result==1234));
{
#ifdef __STDC_IEC_559__
int STDC_IEC_559_is_defined=1;
#else
/* This either means, there is no FP support
*or* the compiler is not C99 enough to define __STDC_IEC_559__
*or* the FP support is not IEEE compliant. */
int STDC_IEC_559_is_defined=0;
#endif
EXPECT("22 floating point is always IEEE",STDC_IEC_559_is_defined);
}
printf("From what I can say with my puny test cases, you are %d%% mainstream\n",100-(100*count)/total);
return 0;
}
Ah, e hice este wiki de la comunidad desde el principio porque pensé que la gente quiere editar mi charlatanería cuando lean esto.
ACTUALIZACIÓN Gracias por su aporte. He agregado algunos casos a partir de sus respuestas y veré si puedo configurar un github para esto como Greg sugirió.
ACTUALIZACIÓN : he creado un repositorio github para esto, el archivo es "gotcha.c":
Responda aquí con parches o nuevas ideas, para que puedan discutirse o aclararse aquí. Los fusionaré en gotcha.c entonces.
fuente
dlsym()
devuelve un vacío * pero está destinado a punteros de datos y funciones. Por lo tanto, puede no ser tan malo depender de esto.Respuestas:
El orden de evaluación de subexpresiones, incluyendo
+
,-
,=
,*
,/
), con la excepción de:&&
y||
),?:
), y,
)no está especificado
Por ejemplo
fuente
boost::spirit
)+
operador no está especificado (los escritores del compilador no necesitan documentar el comportamiento). No viola ninguna regla de punto de secuencia como tal.sdcc 29.7 / ucSim / Z80
printf se bloquea. "O_O"
gcc 4.4@x86_64-suse-linux
gcc 4.4@x86_64-suse-linux (-O2)
clang 2.7@x86_64-suse-linux
open64 4.2.3@x86_64-suse-linux
intel 11.1@x86_64-suse-linux
Turbo C ++ / DOS / Memoria pequeña
Turbo C ++ / DOS / Memoria media
Turbo C ++ / DOS / Memoria compacta
cl65 @ Commodore PET (vice emulador)
Estaré actualizando estos más tarde:
Borland C ++ Builder 6.0 en Windows XP
Visual Studio Express 2010 C ++ CLR, Windows 7 64 bits
(debe compilarse como C ++ porque el compilador CLR no admite C puro)
MINGW64 (pre-análisis gcc-4.5.2)
- http://mingw-w64.sourceforge.net/
Windows de 64 bits utiliza el modelo LLP64: ambos
int
ylong
se definen como 32 bits, lo que significa que ninguno de ellos es lo suficientemente largo para un puntero.avr-gcc 4.3.2 / ATmega168 (Arduino Diecimila)
Los supuestos fallidos son:
El Atmega168 tiene una PC de 16 bits, pero el código y los datos están en espacios de direcciones separados. ¡Atmegas más grande tiene una PC de 22 bits !.
gcc 4.2.1 en MacOSX 10.6, compilado con -arch ppc
fuente
sizeof(void*)>=sizeof(void(*)())
sería más relevante que ==. Lo único que nos importa es "podemos almacenar un puntero de función en un puntero vacío", por lo que la suposición que debe probar es si avoid*
es al menos tan grande como un puntero de función.sizeof(void*)>=sizeof(void(*)())
- vea opengroup.org/onlinepubs/009695399/functions/dlsym.htmlHace mucho tiempo, estaba enseñando C de un libro de texto que tenía
como una pregunta de muestra Falló para un estudiante, porque
sizeof
produce valores de tiposize_t
, noint
,int
en esta implementación fue de 16 bits ysize_t
fue de 32, y fue big-endian. (La plataforma era Lightspeed C en Macintoshes basados en 680x0. Dije que fue hace mucho tiempo).fuente
unsigned long long
ahí. Agregado como prueba 17.z
modificador parasize_t
enteros de tamaño, ylong long
tampoco es compatible con algunas plataformas. Por lo tanto, no hay una forma segura y portátil de formatear o emitir el tamaño impreso de un objeto.Debe incluir los supuestos
++
y las--
suposiciones que hacen.Por ejemplo, es sintácticamente legal, pero produce resultados variables dependiendo de demasiadas cosas para razonar.
Cualquier declaración que tenga
++
(o--
) y una variable que ocurra más de una vez es un problema.fuente
¡Muy interesante!
Otras cosas que se me ocurren podrían ser útiles para verificar:
¿existen punteros de función y punteros de datos en el mismo espacio de direcciones? (Se rompe en máquinas de arquitectura de Harvard como el modo pequeño de DOS. Sin embargo, no sé cómo lo probaría).
si toma un puntero de datos NULL y lo convierte al tipo entero apropiado, ¿tiene el valor numérico 0? (Rompe algunas máquinas realmente antiguas --- ver http://c-faq.com/null/machexamp.html .) Lo mismo ocurre con el puntero de función. Además, pueden ser valores diferentes.
¿Incrementar un puntero más allá del final de su objeto de almacenamiento correspondiente y luego volver de nuevo, produce resultados razonables? (No conozco ninguna máquina en la que realmente se rompa, pero creo que la especificación C no te permite ni siquiera pensar en punteros que no apuntan a (a) el contenido de una matriz o (b) el elemento inmediatamente después de la matriz o (c) NULL. Consulte http://c-faq.com/aryptr/non0based.html .)
¿compara dos punteros a diferentes objetos de almacenamiento con <y> produce resultados consistentes? (Puedo imaginar esta ruptura en máquinas exóticas basadas en segmentos; la especificación prohíbe tales comparaciones, por lo que el compilador tendría derecho a comparar solo la parte de desplazamiento del puntero, y no la parte del segmento).
Hmm Trataré de pensar en algo más.
Editar: se agregaron algunos enlaces aclaratorios a las excelentes preguntas frecuentes de C.
fuente
Creo que debería hacer un esfuerzo para distinguir entre dos clases muy diferentes de supuestos "incorrectos". Una buena mitad (desplazamiento a la derecha y extensión de signo, codificación compatible con ASCII, memoria es lineal, punteros de datos y funciones son compatibles, etc.) son suposiciones bastante razonables para la mayoría de los codificadores C, e incluso podrían incluirse como parte del estándar si C se estuviera diseñando hoy y si no tuviéramos el legado de basura basura de IBM. La otra mitad (cosas relacionadas con el alias de memoria, el comportamiento de las funciones de la biblioteca cuando la memoria de entrada y salida se superponen, suposiciones de 32 bits como esos punteros encajan
int
o que puede usarmalloc
sin un prototipo, esa convención de llamada es idéntica para las funciones variadas y no variables, ...) entra en conflicto con las optimizaciones que los compiladores modernos desean realizar o con la migración a máquinas de 64 bits u otra tecnología nueva.fuente
malloc
sin un prototipo significa no incluir<stdlib.h>
, lo que hacemalloc
que el valor predeterminado seaint malloc(int)
un no-no si desea admitir 64 bits.<stdlib.h>
siempre que incluya otro encabezado que definasize_t
y luego declaremalloc
con un prototipo correcto.Aquí hay una divertida: ¿Qué tiene de malo esta función?
[Respuesta (rot13): Inevnqvp nethzragf borl gur byq X&E cebzbgvba ehyrf, juvpu zrnaf lbh pnaabg hfr 'sybng' (be 'pune' be 'fubeg') va in_net! Naq gur pbzcvyre vf erdhverq abg gb gerng guvf nf n pbzcvyr-gvzr reebe. (TPP qbrf rzvg n jneavat, gubhtu.)]
fuente
Otro es sobre el modo de texto en
fopen
. La mayoría de los programadores asumen que el texto y el binario son iguales (Unix) o que el modo de texto agrega\r
caracteres (Windows). Pero C ha sido portado a sistemas que usan registros de ancho fijo, en los quefputc('\n', file)
en un archivo de texto significa agregar espacios o algo hasta que el tamaño del archivo sea un múltiplo de la longitud del registro.Y aquí están mis resultados:
gcc (Ubuntu 4.4.3-4ubuntu5) 4.4.3 en x86-64
fuente
pow(2, n)
con operaciones de bits.Algunos de ellos no se pueden probar fácilmente desde dentro de C porque es probable que el programa se bloquee en las implementaciones donde la suposición no se cumple.
"Está bien hacer cualquier cosa con una variable con valor de puntero. Solo necesita contener un valor de puntero válido si lo desreferencia".
Lo mismo con los tipos de punto flotante e integral (que no sean
unsigned char
), que pueden tener representaciones de trampa."Los cálculos de enteros terminan. Así que este programa imprime un entero negativo grande".
(C89 solamente.) "Está bien caerse al final de
main
".fuente
gcc -ftrapv -O
, la salida esWe like to think that:
seguida porAborted
main
sin valor: el programa es correcto pero devuelve un estado de terminación indefinido (C89 §2.1.2.2). Con muchas implementaciones (como gcc y compiladores de Unix más antiguos) obtienes lo que estaba en un registro determinado en ese momento. El programa generalmente funciona hasta que se utiliza en un archivo MAKE u otro entorno que verifica el estado de terminación.Bueno, los supuestos de portabilidad clásicos no mencionados aún son
fuente
short
valor fedcab9876543210 (que son 16 dígitos binarios) como los dos bytes 0248ace y fdb97531.Errores de discretización debido a la representación de coma flotante. Por ejemplo, si usa la fórmula estándar para resolver ecuaciones cuadráticas, o diferencias finitas para aproximar derivadas, o la fórmula estándar para calcular varianzas, se perderá precisión debido al cálculo de diferencias entre números similares. El algoritmo de Gauß para resolver sistemas lineales es malo porque los errores de redondeo se acumulan, por lo tanto, uno usa la descomposición QR o LU, la descomposición de Cholesky, SVD, etc. La adición de números de coma flotante no es asociativa. Hay valores denormales, infinitos y NaN. a + b - a ≠ b .
Cadenas: diferencia entre caracteres, puntos de código y unidades de código. Cómo se implementa Unicode en los diversos sistemas operativos; Codificaciones Unicode. No es posible abrir un archivo con un nombre de archivo Unicode arbitrario con C ++ de forma portátil.
Condiciones de carrera, incluso sin subprocesos: si prueba si existe un archivo, el resultado podría volverse inválido en cualquier momento.
ERROR_SUCCESS
= 0fuente
Incluya un cheque para los tamaños enteros. La mayoría de la gente supone que un int es más grande que un short es más grande que un char. Sin embargo, todos estos podrían ser falsos:
sizeof(char) < sizeof(int); sizeof(short) < sizeof(int); sizeof(char) < sizeof(short)
Este código puede fallar (se bloquea el acceso no alineado)
fuente
int *p = (int*)&buf[1];
en c ++, la gente espera que eso también funcione.sizeof(char) < sizeof(int)
es requerido. Por ejemplo, fgetc () devuelve el valor del carácter como un carácter sin signo convertido a int, oEOF
que es un valor negativo.unsigned char
es posible que no tenga bits de relleno, por lo que la única forma de hacerlo es haciendo que int sea más grande que char. Además, (la mayoría de las versiones de) la especificación C requiere que cualquier valor del rango -32767..32767 se pueda almacenar en un int.Un par de cosas sobre los tipos de datos integrados:
char
y ensigned char
realidad son dos tipos distintos (a diferenciaint
ysigned int
que se refieren al mismo tipo de entero con signo).-3/5
podría regresar0
o-1
. El redondeo hacia cero en caso de que un operando fuera negativo solo se garantiza en C99 hacia arriba y C ++ 0x hacia arriba.int
tiene al menos 16 bits, unlong
tiene al menos 32 bits, unlong long
tiene al menos 64 bits. Afloat
puede representar al menos 6 dígitos decimales más significativos correctamente. Adouble
puede representar al menos 10 dígitos decimales más significativos correctamente.Es cierto que en la mayoría de las máquinas tendremos dos complementos y flotadores IEEE 754.
fuente
int mult(int a,int b) { return (long)a*b;}
[por ejemplo, siint
es 32 bits, pero se registra ylong
son 64]. Sin tal requisito, el comportamiento "natural" de la implementación más rápida delong l=mult(1000000,1000000);
seríal
igual a1000000000000
, aunque ese sea un valor "imposible" para unint
.Que tal este:
Ningún puntero de datos puede ser lo mismo que un puntero de función válido.
Esto es VERDADERO para todos los modelos planos, MS-DOS TINY, GRANDE y GRANDE, falso para el modelo MS-DOS PEQUEÑO, y casi siempre falso para los modelos MEDIO y COMPACTO (depende de la dirección de carga, necesitará un DOS muy antiguo para hazlo verdad).
No puedo escribir una prueba para esto
Y lo que es peor: se pueden comparar los punteros lanzados a ptrdiff_t. Esto no es cierto para el modelo MS-DOS LARGE (la única diferencia entre LARGE y HUGE es que HUGE agrega el código del compilador para normalizar los punteros).
No puedo escribir una prueba porque el entorno donde esta bomba dura no asignará un búfer mayor de 64K, por lo que el código que demuestra que se bloqueará en otras plataformas.
Esta prueba en particular pasaría en un sistema ahora difunto (tenga en cuenta que depende de lo interno de malloc):
fuente
EDITAR: actualizado a la última versión del programa
Solaris-SPARC
gcc 3.4.6 en 32 bits
gcc 3.4.6 en 64 bits
y con SUNStudio 11 32 bit
y con SUNStudio 11 64 bit
fuente
Puede usar text-mode (
fopen("filename", "r")
) para leer cualquier tipo de archivo de texto.Si bien , en teoría, esto debería funcionar bien, si también lo usa
ftell()
en su código, y su archivo de texto tiene finales de línea de estilo UNIX, en algunas versiones de la biblioteca estándar de Windows,ftell()
a menudo devolverá valores no válidos. La solución es utilizar el modo binario en su lugar (fopen("filename", "rb")
).fuente
gcc 3.3.2 en AIX 5.3 (sí, necesitamos actualizar gcc)
fuente
Una suposición que algunos pueden hacer en C ++ es que a
struct
se limita a lo que puede hacer en C. El hecho es que, en C ++, astruct
es como unclass
excepto que tiene todo público por defecto.Estructura C ++:
fuente
Las funciones matemáticas estándar en diferentes sistemas no dan resultados idénticos.
fuente
Visual Studio Express 2010 en 32 bits x86.
fuente
Vía Codepad.org (
C++: g++ 4.1.2 flags: -O -std=c++98 -pedantic-errors -Wfatal-errors -Werror -Wall -Wextra -Wno-missing-field-initializers -Wwrite-strings -Wno-deprecated -Wno-unused -Wno-non-virtual-dtor -Wno-variadic-macros -fmessage-length=0 -ftemplate-depth-128 -fno-merge-constants -fno-nonansi-builtins -fno-gnu-keywords -fno-elide-constructors -fstrict-aliasing -fstack-protector-all -Winvalid-pch
).Tenga en cuenta que Codepad no tenía
stddef.h
. Eliminé la prueba 9 debido al teclado usando advertencias como errores. También cambié el nombre de lacount
variable porque ya estaba definida por alguna razón.fuente
¿Qué tal el desplazamiento a la derecha por cantidades excesivas? ¿Está permitido por el estándar o vale la pena probarlo?
¿El Estándar C especifica el comportamiento del siguiente programa:
En al menos un compilador que uso, ese código fallará a menos que el argumento de print_string sea un "char const *". ¿La norma permite tal restricción?
Algunos sistemas permiten producir punteros a 'int' no alineados y otros no. Puede valer la pena probarlo.
fuente
<<
y>>
). C99 tiene un lenguaje idéntico en §6.5.7-3.putch
(¿por qué no usaste el estándarputchar
?), No puedo ver ningún comportamiento indefinido en tu programa. C89 §3.1.4 especifica que "un literal de cadena de caracteres tiene [...] tipo 'array of char'" (nota: noconst
), y que "si el programa intenta modificar un literal de cadena [...], el comportamiento es indefinido" . ¿Qué compilador es ese y cómo traduce este programa?Para su información, para aquellos que tienen que traducir sus habilidades en C a Java, aquí hay algunas trampas.
En Java, char es de 16 bits y está firmado. El byte es de 8 bits y está firmado.
siempre es de 64 bits, las referencias pueden ser de 32 o 64 bits (si tiene más de una aplicación con más de 32 GB), las JVM de 64 bits suelen utilizar referencias de 32 bits.
El cambio está enmascarado para que yo << 64 == i == i << -64, i << 63 == i << -1
ByteOrder.nativeOrder () puede ser BIG_ENDIAN o LITTLE_ENDIAN
i = i++
nunca cambiai
El tamaño de las colecciones y las matrices siempre es de 32 bits, independientemente de si la JVM es de 32 bits o de 64 bits.
char es de 16 bits, short es de 16 bits, int es de 32 bits y long es de 64 bits.
fuente