¿Qué pertenece a una herramienta educativa para demostrar las suposiciones injustificadas que las personas hacen en C / C ++?

Me gustaría preparar una pequeña herramienta educativa para SO, que debería ayudar a los progtwigdores principiantes (e intermedios) a reconocer y desafiar sus suposiciones injustificadas en C, C ++ y sus plataformas.

Ejemplos:

  • “enteros envuelven”
  • “Todos tienen ASCII”
  • “Puedo almacenar un puntero a la función en un vacío *”

Pensé que un pequeño progtwig de prueba podría ejecutarse en varias plataformas, que ejecuta las suposiciones “plausibles” que, según nuestra experiencia en SO, usualmente hacen muchos desarrolladores convencionales sin experiencia o semiexperiencia y registran las formas en que se dividen en diversas máquinas.

El objective de esto no es probar que es “seguro” hacer algo (lo que sería imposible de hacer, las pruebas prueban solo cualquier cosa si se rompen), sino demostrarle al individuo más incomprendido cómo la expresión más discreta romper en una máquina diferente, si tiene un comportamiento definido o no definido. .

Para lograr esto, me gustaría preguntarle:

  • ¿Cómo se puede mejorar esta idea?
  • ¿Qué pruebas serían buenas y cómo deberían verse?
  • ¿Ejecutarías las pruebas en las plataformas en las que puedes tener acceso y publicarás los resultados, de modo que terminemos con una base de datos de plataformas, cómo difieren y por qué se permite esta diferencia?

Aquí está la versión actual para el juguete de prueba:

#include  #include  #include  #include  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 AZ is in a block",('Z'-'A')+1==26); EXPECT("02 big letters come before small letters",('A'=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<=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 esta wiki de la comunidad desde el principio porque pensé que la gente quería editar mi blabber cuando leen esto.

ACTUALIZACIÓN Gracias por su aporte. He agregado algunos casos de tus respuestas y veré si puedo configurar un github para esto como sugirió Greg.

ACTUALIZACIÓN : he creado un repository github para esto, el archivo es “gotcha.c”:

  • http://github.com/lutherblissett/disenchanter

Responda aquí con parches o nuevas ideas, para que puedan ser discutidos o aclarados aquí. Los fusionaré en gotcha.c luego.

El orden de evaluación de las subexpresiones, incluido

  • los argumentos de una llamada de función y
  • operandos de operadores (p. ej., + , - , = , * , / ), con la excepción de:
    • los operadores lógicos binarios ( && y || ),
    • el operador condicional ternario ( ?: y
    • el operador de coma ( , )

no especificado

Por ejemplo

  int Hello() { return printf("Hello"); /* printf() returns the number of characters successfully printed by it */ } int World() { return printf("World !"); } int main() { int a = Hello() + World(); //might print Hello World! or World! Hello /** ^ | Functions can be called in either order **/ return 0; } 

sdcc 29.7 / ucSim / Z80

 We like to think that: ..09a minus shifts backwards but '(t=-1,(15< 

printf se bloquea. "O_O"


gcc 4.4@x86_64-suse-linux

 We like to think that: ..05 int has the size of pointers but 'sizeof(int)==sizeof(void*)' is false. ..08 overshifting is okay but '(1< 

gcc 4.4@x86_64-suse-linux (-O2)

 We like to think that: ..05 int has the size of pointers but 'sizeof(int)==sizeof(void*)' is false. ..08 overshifting is okay but '(1< 

clang 2.7@x86_64-suse-linux

 We like to think that: ..05 int has the size of pointers but 'sizeof(int)==sizeof(void*)' is false. ..08 overshifting is okay but '(1< 

open64 4.2.3@x86_64-suse-linux

 We like to think that: ..05 int has the size of pointers but 'sizeof(int)==sizeof(void*)' is false. ..08 overshifting is okay but '(1< 

intel 11.1@x86_64-suse-linux

 We like to think that: ..05 int has the size of pointers but 'sizeof(int)==sizeof(void*)' is false. ..08 overshifting is okay but '(1< 

Turbo C ++ / DOS / Memoria pequeña

 We like to think that: ..09a minus shifts backwards but '(t=-1,(15< 

Turbo C ++ / DOS / Memoria Media

 We like to think that: ..09a minus shifts backwards but '(t=-1,(15<=sizeof(void(*)())' is false. ..16 malloc()=NULL means out of memory but '(malloc(0)!=NULL)' is false. ..19-2 short 

Turbo C ++ / DOS / memoria compacta

 We like to think that: ..05 int has the size of pointers but 'sizeof(int)==sizeof(void*)' is false. ..09a minus shifts backwards but '(t=-1,(15< 

cl65 @ Commodore PET (vice emulador)

texto alternativo http://sofes.miximages.com/c%2B%2B/2hh0zmc.png


Los actualizaré más tarde:


Borland C ++ Builder 6.0 en Windows XP

 ..04 a char is signed but 'CHAR_MIN==SCHAR_MIN' is false. ..08 overshifting is okay but '(1< 

Visual Studio Express 2010 C ++ CLR, Windows 7 64bit

(debe comstackrse como C ++ porque el comstackdor CLR no admite C puro)

 We like to think that: ..08 overshifting is okay but '(1< 

MINGW64 (gcc-4.5.2 prerelase)

- http://mingw-w64.sourceforge.net/

 We like to think that: ..05 int has the size of pointers but 'sizeof(int)==sizeof(void*)' is false. ..05a long has at least the size of pointers but 'sizeof(long)>=sizeof(void*)' is false. ..08 overshifting is okay but '(1< 

64 bit Windows usa el modelo LLP64: tanto int como long están definidos como 32 bits, lo que significa que ninguno es lo suficientemente largo para un puntero.


avr-gcc 4.3.2 / ATmega168 (Arduino Diecimila)

Las suposiciones fallidas son:

 ..14 i++ is structly left to right ..16 malloc()=NULL means out of memory ..19-2 short 

El Atmega168 tiene una PC de 16 bits, pero el código y los datos están en espacios de direcciones separados. Los Atmegas más grandes tienen una PC de 22 bits.


gcc 4.2.1 en MacOSX 10.6, comstackdo con -arch ppc

 We like to think that: ..09a minus shifts backwards but '(t=-1,(15< 

Hace mucho tiempo, yo estaba enseñando C de un libro de texto que tenía

 printf("sizeof(int)=%d\n", sizeof(int)); 

como una pregunta de muestra. Falló para un alumno, ya que sizeof produce valores de tipo size_t , not int , int en esta implementación era de 16 bits y size_t era 32, y era big-endian. (La plataforma era Lightspeed C en Macintoshes basados ​​en 680×0. Dije que fue hace mucho tiempo).

Necesita incluir el ++ y las suposiciones de las personas.

 a[i++]= i; 

Por ejemplo, es sintácticamente legal, pero produce resultados variables dependiendo de muchas cosas para razonar.

Cualquier statement que tenga ++ (o -- ) y una variable que ocurra más de una vez es un problema.

¡Muy interesante!

Otras cosas que puedo pensar podrían ser útiles para verificar:

  • ¿Los punteros de función y los punteros de datos existen en el mismo espacio de direcciones? (Se rompe en las máquinas de architecture de Harvard, como el modo pequeño de DOS. Sin embargo, no sé cómo lo probarías).

  • si toma un puntero de datos NULL y lo convierte al tipo de entero apropiado, ¿tiene el valor numérico 0? (Se rompe en algunas máquinas realmente antiguas — vea http://c-faq.com/null/machexamp.html .) Ídem con puntero de función. Además, pueden ser valores diferentes.

  • ¿Incrementa un puntero más allá del final de su objeto de almacenamiento correspondiente y luego lo vuelve a generar resultados razonables? (No sé de ninguna máquina que realmente se rompa, pero creo que la especificación C no le permite 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 .)

  • ¿comparar dos punteros a diferentes objetos de almacenamiento con producir resultados consistentes? (Puedo imaginar esta ruptura en máquinas basadas en segmentos exóticos, la especificación prohíbe tales comparaciones, por lo que el comstackdor tendría derecho a comparar la parte de desplazamiento del puntero solamente, y no la parte del segmento).

Hmm. Trataré de pensar en algunos más.

Editar: se agregaron algunos enlaces aclaratorios a las excelentes preguntas frecuentes de C.

Creo que deberías hacer un esfuerzo para distinguir entre dos clases muy diferentes de suposiciones “incorrectas”. Una buena mitad (desplazamiento a la derecha y extensión de señal, encoding compatible con ASCII, memoria lineal, indicadores de datos y funciones 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ésemos IBM junk grandfathered-in heredado. La otra mitad (cosas relacionadas con el alias de memoria, el comportamiento de las funciones de biblioteca cuando se superponen la memoria de entrada y salida, suposiciones de 32 bits como punteros encajan en int o que puede usar malloc sin un prototipo, esa convención de llamadas es idéntica para variadis y non -funciones variables, …) entran en conflicto con las optimizaciones que los comstackdores modernos quieren realizar o con la migración a máquinas de 64 bits u otra tecnología nueva.

Bueno, las suposiciones de portabilidad clásica que aún no se mencionan son

  • suposiciones sobre el tamaño de los tipos integrales
  • endianness
  • Errores de discretización debido a la representación de coma flotante. Por ejemplo, si utiliza la fórmula estándar para resolver ecuaciones cuadráticas, o diferencias finitas para derivadas aproximadas, o la fórmula estándar para calcular las variaciones, la precisión se perderá debido al cálculo de las diferencias entre números similares. El algoritmo Gauß para resolver sistemas lineales es malo porque se acumulan errores de redondeo, por lo que uno usa descomposición QR o LU, descomposición de Cholesky, SVD, etc. La sum de números de coma flotante no es asociativa. Hay valores denormal, infinito y NaN. a + bab .

  • 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 enhebrar: si comprueba si existe un archivo, el resultado podría perder validez en cualquier momento.

  • ERROR_SUCCESS = 0

Aquí hay uno divertido: ¿Qué pasa con esta función?

 float sum(unsigned int n, ...) { float v = 0; va_list ap; va_start(ap, n); while (n--) v += va_arg(ap, float); va_end(ap); return v; } 

[Respuesta (rot13): Inevnqvp nethzragf borl gur byq X & E cebzbgvba ehyrf, juvpu zrnaf lbh pnaabg hfr ‘sybng’ (ser ‘pune’ ser ‘fubeg’) va in_net! Naq gur pbzcvyre vf erdhverq abg gb gerng guvf nf n pbzcvyr-gvzr reebe. (TPP qbrf rzvg n jneavat, gubhtu.)]

 EXPECT("## pow() gives exact results for integer arguments", pow(2, 4) == 16); 

Otra es sobre el modo texto en fopen . La mayoría de los progtwigdores asumen que el texto y el binario son los mismos (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 cuales fputc('\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

 We like to think that: ..05 int has the size of pointers but 'sizeof(int)==sizeof(void*)' is false. ..08 overshifting is okay but '(1< 

Algunos de ellos no se pueden probar fácilmente desde dentro de C porque es probable que el progtwig se bloquee en las implementaciones donde la suposición no es válida.


“Está bien hacer cualquier cosa con una variable con valor de puntero. Solo necesita contener un valor de puntero válido si lo desreferencia”.

 void noop(void *p); /* A no-op function that the compiler doesn't know to optimize away */ int main () { char *p = malloc(1); free(p); noop(p); /* may crash in implementations that verify pointer accesses */ noop(p - 42000); /* and if not the previous instruction, maybe this one */ } 

Lo mismo ocurre con los tipos de punto integral y flotante (que unsigned char ), que tienen representaciones de trampa.


“Los cálculos enteros se ajustan. Entonces, este progtwig imprime un entero negativo grande”.

 #include  int main () { printf("%d\n", INT_MAX+1); /* may crash due to signed integer overflow */ return 0; } 

(C89 solamente). “Está bien caer al final de la pista main “.

 #include  int main () { puts("Hello."); } /* The status code is 7 on many implementations. */ 

Incluye un cheque para tamaños enteros. La mayoría de las personas asume que un int es más grande que un corto es más grande que un char. Sin embargo, estos podrían ser todos falsos: sizeof(char) < sizeof(int); sizeof(short) < sizeof(int); sizeof(char) < sizeof(short) sizeof(char) < sizeof(int); sizeof(short) < sizeof(int); sizeof(char) < sizeof(short)

Este código puede fallar (se bloquea el acceso desalineado)

 unsigned char buf[64]; int i = 234; int *p = &buf[1]; *p = i; i = *p; 

Un par de cosas sobre los tipos de datos incorporados:

  • char y signed char son en realidad dos tipos distintos (a diferencia de int y signed int que hacen referencia al mismo tipo de entero con signo).
  • los enteros con signo no son necesarios para usar el complemento de dos. El complemento de uno y el signo + magnitud también son representaciones válidas de números negativos. Esto hace que las operaciones de bits que implican números negativos se definan como implementadas .
  • Si asigna un número entero fuera de rango a una variable entera con signo, el comportamiento está definido por la implementación .
  • En C90, -3/5 podría devolver 0 o -1 . Redondear hacia cero en caso de que un operando fuera negativo solo está garantizado en C99 hacia arriba y C ++ 0x hacia arriba.
  • No hay garantías de tamaño exactas para los tipos incorporados. El estándar solo cubre requisitos mínimos, como un int tiene al menos 16 bits, un long tiene al menos 32 bits, un long long tiene al menos 64 bits. Un float puede representar al menos 6 dígitos decimales más significativos correctamente. Un double puede representar al menos 10 dígitos decimales más significativos correctamente.
  • IEEE 754 no es obligatorio para representar números de coma flotante.

Es cierto que en la mayoría de las máquinas tendremos complemento dos y flotadores IEEE 754.

EDITAR: actualizado a la última versión del progtwig

Solaris-SPARC

gcc 3.4.6 en 32 bits

 We like to think that: ..08 overshifting is okay but '(1< 

gcc 3.4.6 en 64 bit

 We like to think that: ..05 int has the size of pointers but 'sizeof(int)==sizeof(void*)' is false. ..08 overshifting is okay but '(1< 

y con SUNStudio 11 32 bit

 We like to think that: ..08 overshifting is okay but '(1< 

y con SUNStudio 11 64 bit

 We like to think that: ..05 int has the size of pointers but 'sizeof(int)==sizeof(void*)' is false. ..08 overshifting is okay but '(1< 

Que tal este:

No data pointer can ever be the same as a valid function pointer.

This is TRUE for all flat models, MS-DOS TINY, LARGE, and HUGE models, false for MS-DOS SMALL model, and almost always false for MEDIUM and COMPACT models (depends on load address, you will need a really old DOS to make it true).

I can’t write a test for this

And worse: pointers casted to ptrdiff_t may be compared. This not true for MS-DOS LARGE model (the only difference between LARGE and HUGE is HUGE adds compiler code to normalize pointers).

I can’t write a test because the environment where this bombs hard won’t allocate a buffer greater than 64K so the code that demonstrates it would crash on other platforms.

This particular test would pass on one now-defunct system (notice it depends on the internals of malloc):

  char *ptr1 = malloc(16); char *ptr2 = malloc(16); if ((ptrdiff_t)ptr2 - 0x20000 == (ptrdiff_t)ptr1) printf("We like to think that unrelated pointers are equality comparable when cast to the appropriate integer, but they're not."); 

You can use text-mode ( fopen("filename", "r") ) to read any sort of text file.

While this should in theory work just fine, if you also use ftell() in your code, and your text file has UNIX-style line-endings, in some versions of the Windows standard library, ftell() will often return invalid values. The solution is to use binary mode instead ( fopen("filename", "rb") ).

gcc 3.3.2 on AIX 5.3 (yeah, we need to update gcc)

 We like to think that: ..04 a char is signed but 'CHAR_MIN==SCHAR_MIN' is false. ..09a minus shifts backwards but '(t=-1,(15< 

An assumption that some may do in C++ is that a struct is limited to what it can do in C. The fact is that, in C++, a struct is like a class except that it has everything public by default.

C++ struct:

 struct Foo { int number1_; //this is public by default //this is valid in C++: private: void Testing1(); int number2_; protected: void Testing2(); }; 

Standard math functions on different systems don’t give identical results.

Visual Studio Express 2010 on 32-bit x86.

 Z:\sandbox>cl testtoy.c Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.30319.01 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved. testtoy.c testtoy.c(54) : warning C4293: '<<' : shift count negative or too big, undefined behavior Microsoft (R) Incremental Linker Version 10.00.30319.01 Copyright (C) Microsoft Corporation. All rights reserved. /out:testtoy.exe testtoy.obj Z:\sandbox>testtoy.exe We like to think that: ..08 overshifting is okay but '(1< 

Via 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 ) .

Note that Codepad did not have stddef.h . I removed test 9 due to codepad using warnings as errors. I also renamed the count variable since it was already defined for some reason.

 We like to think that: ..08 overshifting is okay but '(1< 

How about right-shifting by excessive amounts–is that allowed by the standard, or worth testing?

Does Standard C specify the behavior of the following program:

void print_string(char *st)
 {
  char ch;
  while((ch = *st++) != 0)
    putch(ch); /* Assume this is defined */
 }
int main(void)
 {
  print_string("Hello");
   return 0;
 }

On at least one compiler I use, that code will fail unless the argument to print_string is a “char const *”. Does the standard permit such a restriction?

Some systems allow one to produce pointers to unaligned ‘int’s and others don’t. Might be worth testing.

FYI, For those who have to translate their C skills to Java, here are a few gotchas.

 EXPECT("03 a char is 8 bits",CHAR_BIT==8); EXPECT("04 a char is signed",CHAR_MIN==SCHAR_MIN); 

In Java, char is 16-bit and signed. byte is 8-bit and signed.

 /* not true for Windows-64 */ EXPECT("05a long has at least the size of pointers",sizeof(long)>=sizeof(void*)); 

long is always 64-bit, references can be 32-bit or 64-bit (if you have more than an app with more than 32 GB) 64-bit JVMs typically use 32-bit references.

 EXPECT("08 overshifting is okay",(1< 

The shift is masked so that i << 64 == i == i << -64, i << 63 == i << -1

 EXPECT("13 The smallest bits always come first",(t=0x1234,0x34==*(char*)&t)); 

ByteOrder.nativeOrder() can be BIG_ENDIAN or LITTLE_ENDIAN

 EXPECT("14 i++ is strictly left to right",(i=0,a[i++]=i,a[0]==1)); 

i = i++ never changes i

 /* suggested by David Thornley */ EXPECT("17 size_t is unsigned int",sizeof(size_t)==sizeof(unsigned int)); 

The size of collections and arrays is always 32-bit regardless of whether the JVM is 32-bit or 64-bit.

 EXPECT("19-1 char 

char is 16-bit, short is 16-bit, int is 32-bit and long is 64-bit.