Eu gostaria de preparar uma pequena ferramenta educacional para SO, que deve ajudar programadores iniciantes (e intermediários) a reconhecer e desafiar suas suposições injustificadas em C, C ++ e suas plataformas.
Exemplos:
- "números inteiros"
- "todo mundo tem ASCII"
- "Eu posso armazenar um ponteiro de função em um vazio *"
Imaginei que um pequeno programa de teste pudesse ser executado em várias plataformas, que executam as suposições "plausíveis" que, a partir de nossa experiência em SO, geralmente são feitas por muitos desenvolvedores mainstream inexperientes / semiexperientes e registram a maneira como eles quebram em diversas máquinas.
O objetivo disso não é provar que é "seguro" fazer algo (o que seria impossível de fazer, os testes provam qualquer coisa se eles quebrarem), mas demonstrar até mesmo ao indivíduo mais compreensivo como a expressão mais discreta quebrar em uma máquina diferente, se ela tiver um comportamento indefinido ou definido pela implementação. .
Para conseguir isso, gostaria de perguntar:
- Como essa ideia pode ser melhorada?
- Quais testes seriam bons e como eles deveriam ser?
- Você executaria os testes nas plataformas nas quais você pode colocar as mãos e publicar os resultados, para que possamos terminar com um banco de dados de plataformas, como elas diferem e por que essa diferença é permitida?
Aqui está a versão atual do brinquedo de teste:
#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 eu criei este wiki da comunidade desde o início, porque achei que as pessoas querem editar minha tagarelice quando lêem isso.
ATUALIZAÇÃO Obrigado pela sua contribuição. Adicionei alguns casos de suas respostas e verei se posso configurar um github para isso, como Greg sugeriu.
UPDATE : Eu criei um repositório no Github para isso, o arquivo é "gotcha.c":
Responda aqui com correções ou novas idéias, para que possam ser discutidas ou esclarecidas aqui. Vou fundi-los em gotcha.c então.
fonte
dlsym()
retorna um nulo *, mas destina-se a indicadores de dados e funções. Portanto, pode não ser tão ruim depender disso.Respostas:
A ordem de avaliação das subexpressões, incluindo
+
,-
,=
,*
,/
), com a excepção de:&&
e||
),?:
) e,
)é não especificado
Por exemplo
fonte
boost::spirit
)+
operador não é especificada (os escritores do compilador não precisam documentar o comportamento). Não viola nenhuma regra de ponto de sequência como tal.sdcc 29.7 / ucSim / Z80
printf trava. "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 / Memória pequena
Turbo C ++ / DOS / Memória Média
Memória Turbo C ++ / DOS / Compacta
cl65 @ Commodore PET (vice-emulador)
Vou atualizá-los mais tarde:
Borland C ++ Builder 6.0 no Windows XP
Visual Studio Express 2010 C ++ CLR, Windows 7 de 64 bits
(deve ser compilado como C ++ porque o compilador CLR não suporta C puro)
MINGW64 (pré -elas gcc-4.5.2)
- http://mingw-w64.sourceforge.net/
O Windows de 64 bits usa o modelo LLP64: Ambos
int
elong
são definidos como 32 bits, o que significa que nenhum é longo o suficiente para um ponteiro.avr-gcc 4.3.2 / ATmega168 (Arduino Diecimila)
As suposições com falha são:
O Atmega168 possui um PC de 16 bits, mas o código e os dados estão em espaços de endereço separados. Atmegas maiores têm um PC de 22 bits !.
gcc 4.2.1 no MacOSX 10.6, compilado com -arch ppc
fonte
sizeof(void*)>=sizeof(void(*)())
seria mais relevante que ==. Tudo o que nos preocupa é "podemos armazenar um ponteiro de função em um ponteiro nulo", então a suposição que você precisa testar é se avoid*
é pelo menos tão grande quanto um ponteiro de função.sizeof(void*)>=sizeof(void(*)())
- ver opengroup.org/onlinepubs/009695399/functions/dlsym.htmlHá muito tempo, eu estava ensinando C de um livro que tinha
como uma pergunta de amostra. Ele falhou para um aluno, porque
sizeof
gera valores do tiposize_t
, nãoint
,int
nesta implementação tinha 16 bits esize_t
32, e era big-endian. (A plataforma era o Lightspeed C em Macintoshes baseados em 680x0. Eu disse isso há muito tempo.)fonte
unsigned long long
lá. Adicionado como Teste 17.z
modificador parasize_t
números inteiros de tamanho elong long
também não é suportado em algumas plataformas. Portanto, não há uma maneira portátil e segura de formatar ou converter o tamanho impresso de um objeto.Você precisa incluir os
++
e--
suposições que as pessoas fazem.Por exemplo, é sintaticamente legal, mas produz resultados variados, dependendo de muitas coisas para raciocinar.
Qualquer declaração que tenha
++
(ou--
) e uma variável que ocorra mais de uma vez é um problema.fonte
Muito interessante!
Outras coisas em que posso pensar podem ser úteis para verificar:
existem indicadores de função e dados no mesmo espaço de endereço? (Quebras nas máquinas de arquitetura de Harvard, como o modo pequeno do DOS. Porém, não sei como você testaria isso.)
se você pegar um ponteiro de dados NULL e o converter no tipo inteiro apropriado, ele possui o valor numérico 0? (Quebra em algumas máquinas realmente antigas - consulte http://c-faq.com/null/machexamp.html .) O mesmo vale para o ponteiro de função. Além disso, eles podem ter valores diferentes.
incrementar um ponteiro após o final do seu objeto de armazenamento correspondente e, em seguida, voltar novamente, causa resultados razoáveis? (Eu não conheço nenhuma máquina na qual ele realmente interrompa, mas acredito que a especificação C não permite que você pense em ponteiros que não apontam para (a) o conteúdo de uma matriz ou (b) o elemento imediatamente após a matriz ou (c) NULL. Consulte http://c-faq.com/aryptr/non0based.html .)
comparar dois ponteiros com diferentes objetos de armazenamento com <e> produz resultados consistentes? (Eu posso imaginar essa quebra em máquinas exóticas baseadas em segmentos; as especificações proíbem essas comparações, para que o compilador tenha o direito de comparar apenas a parte deslocada do ponteiro, e não a parte do segmento.)
Hmm. Vou tentar pensar um pouco mais.
Edit: Adicionado alguns links de esclarecimento para o excelente C FAQ.
fonte
Eu acho que você deve fazer um esforço para distinguir entre duas classes muito diferentes de suposições "incorretas". Uma boa metade (deslocamento à direita e extensão de sinal, codificação compatível com ASCII, memória é linear, ponteiros de dados e funções são compatíveis etc.) são suposições bastante razoáveis para a maioria dos codificadores C criar e podem até ser incluídas como parte do padrão se C estivesse sendo projetado hoje e se não tivéssemos o legado de sucata IBM herdado. A outra metade (coisas relacionadas ao alias de memória, comportamento das funções da biblioteca quando a memória de entrada e saída se sobrepõe, suposições de 32 bits como as que os ponteiros se encaixam
int
ou que você pode usarmalloc
sem um protótipo, essa convenção de chamada é idêntica para funções variadas e não variadas, ...) conflitam com as otimizações que os compiladores modernos desejam executar ou com a migração para máquinas de 64 bits ou outra nova tecnologia.fonte
malloc
sem protótipo significa não incluir<stdlib.h>
, o que fazmalloc
com que o padrão sejaint malloc(int)
um não-não, se você deseja oferecer suporte a 64 bits.<stdlib.h>
, desde que inclua outro cabeçalho que definasize_t
e, em seguida, declaremalloc
com um protótipo correto.Aqui está uma divertida: O que há de errado com esta função?
[Resposta (rot13): Inevnqvp nethzragf borl gur byq X&E cebzbgvba ehyrf, juvpu zrnaf lbh pnaabg hfr 'sybng (seja' pune 'seja' fubeg ') va in_net! Naq gur pbzcvyre vf erdhverq abg gb gerng guvf nf n pbzcvyr-gvzr reebe. (TPP qbrf rzvg n jneavat, gubhtu.)]
fonte
Outro é sobre o modo de texto no
fopen
. A maioria dos programadores assume que texto e binário são iguais (Unix) ou que o modo de texto adiciona\r
caracteres (Windows). Mas C foi portado para sistemas que usam registros de largura fixa, nos quaisfputc('\n', file)
em um arquivo de texto significa adicionar espaços ou algo até que o tamanho do arquivo seja múltiplo do comprimento do registro.E aqui estão meus resultados:
gcc (Ubuntu 4.4.3-4ubuntu5) 4.4.3 no x86-64
fonte
pow(2, n)
com operações de bits.Alguns deles não podem ser facilmente testados a partir do C, porque o programa provavelmente trava nas implementações em que a suposição não se aplica.
"Não há problema em fazer qualquer coisa com uma variável com valor de ponteiro. Ele só precisa conter um valor de ponteiro válido se você a derereçar."
O mesmo ocorre com os tipos de ponto flutuante e integral (que não sejam
unsigned char
), que podem ter representações de trap."Cálculos inteiros são contornados. Portanto, este programa imprime um número inteiro negativo grande."
(Somente C89.) "Não há problema em cair no final de
main
."fonte
gcc -ftrapv -O
, a saída éWe like to think that:
seguida porAborted
main
sem valor: o programa está correto, mas retorna um status de finalização indefinido (C89 §2.1.2.2). Com muitas implementações (como gcc e compiladores unix mais antigos), você obtém o que estava em um determinado registro naquele momento. O programa normalmente funciona até ser usado em um makefile ou outro ambiente que verifica o status da finalização.Bem, as suposições clássicas de portabilidade ainda não mencionadas são
fonte
short
valor fedcab9876543210 (com 16 dígitos binários) como os dois bytes 0248ace e fdb97531.Erros de discretização devido à representação de ponto flutuante. Por exemplo, se você usar a fórmula padrão para resolver equações quadráticas, ou diferenças finitas para aproximar derivadas, ou a fórmula padrão para calcular variações, a precisão será perdida devido ao cálculo das diferenças entre números semelhantes. O algoritmo de Gauß para resolver sistemas lineares é ruim, porque os erros de arredondamento se acumulam; portanto, utiliza-se decomposição QR ou LU, decomposição de Cholesky, SVD etc. A adição de números de ponto flutuante não é associativa. Existem valores anormais, infinitos e NaN. a + b - a ≠ b .
Strings: diferença entre caracteres, pontos de código e unidades de código. Como o Unicode é implementado nos vários sistemas operacionais; Codificações Unicode. Abrir um arquivo com um nome de arquivo Unicode arbitrário não é possível com o C ++ de maneira portátil.
Condições de corrida, mesmo sem encadeamento: se você testar se existe um arquivo, o resultado poderá se tornar inválido a qualquer momento.
ERROR_SUCCESS
= 0fonte
Inclua uma verificação para tamanhos inteiros. A maioria das pessoas assume que um int é maior que um short e maior que um char. No entanto, tudo isso pode ser falso:
sizeof(char) < sizeof(int); sizeof(short) < sizeof(int); sizeof(char) < sizeof(short)
Esse código pode falhar (falha no acesso não alinhado)
fonte
int *p = (int*)&buf[1];
em c ++, as pessoas esperam que isso funcione também.sizeof(char) < sizeof(int)
É necessário. Por exemplo, fgetc () retorna o valor do caractere como um caractere não assinado convertido em int ouEOF
que é um valor negativo.unsigned char
pode não ter bits de preenchimento, então a única maneira de fazer isso é tornando int maior que char. Além disso, (a maioria das versões) das especificações C exigem que qualquer valor do intervalo -32767..32767 possa ser armazenado em um int.Algumas coisas sobre os tipos de dados internos:
char
esigned char
na verdade, são dois tipos distintos (diferentesint
esigned int
que se referem ao mesmo tipo inteiro assinado).-3/5
poderia retornar0
ou-1
. O arredondamento para zero no caso de um operando ser negativo é garantido apenas em C99 para cima e C ++ 0x para cima.int
com pelo menos 16 bits, umlong
com pelo menos 32 bits e umlong long
com pelo menos 64 bits. Afloat
pode pelo menos representar 6 dígitos decimais mais significativos corretamente. Adouble
pode pelo menos representar 10 dígitos decimais mais significativos corretamente.É certo que, na maioria das máquinas, teremos dois complementos e flutuadores IEEE 754.
fonte
int mult(int a,int b) { return (long)a*b;}
[por exemplo, seint
for de 32 bits, mas registra elong
é de 64]. Sem esse requisito, o comportamento "natural" da implementação mais rápida delong l=mult(1000000,1000000);
serial
igual a1000000000000
, mesmo que seja um valor "impossível" para umint
.Que tal este:
Nenhum ponteiro de dados pode ser o mesmo que um ponteiro de função válido.
Isso é VERDADEIRO para todos os modelos simples, modelos MS-DOS TINY, LARGE e HUGE, falso para o modelo SM-DOS SMALL e quase sempre falso para os modelos MÉDIO e COMPACTO (depende do endereço de carregamento, você precisará de um DOS realmente antigo). faça verdade).
Não posso escrever um teste para isso
E pior: os ponteiros convertidos para ptrdiff_t podem ser comparados. Isso não é verdade para o modelo MS-DOS LARGE (a única diferença entre LARGE e HUGE é HUGE adiciona código do compilador para normalizar os ponteiros).
Não consigo escrever um teste porque o ambiente em que essa bomba é difícil não aloca um buffer maior que 64K, portanto o código que a demonstra falha em outras plataformas.
Esse teste em particular passaria em um sistema agora extinto (observe que depende dos elementos internos do malloc):
fonte
EDIT: Atualizado para a última versão do programa
Solaris-SPARC
gcc 3.4.6 em 32 bits
gcc 3.4.6 em 64 bits
e com o SUNStudio 11 de 32 bits
e com o SUNStudio 11 de 64 bits
fonte
Você pode usar o modo de texto (
fopen("filename", "r")
) para ler qualquer tipo de arquivo de texto.Embora isso , em teoria, funcione bem, se você também usar
ftell()
no seu código e seu arquivo de texto tiver terminações de linha no estilo UNIX, em algumas versões da biblioteca padrão do Windows,ftell()
retornará frequentemente valores inválidos. A solução é usar o modo binário (fopen("filename", "rb")
).fonte
gcc 3.3.2 no AIX 5.3 (sim, precisamos atualizar o gcc)
fonte
Uma suposição de que alguns podem fazer em C ++ é que a
struct
é limitado ao que pode fazer em C. O fato é que, em C ++, astruct
é como aclass
exceto que tem tudo público por padrão.Estrutura C ++:
fonte
As funções matemáticas padrão em sistemas diferentes não fornecem resultados idênticos.
fonte
Visual Studio Express 2010 em x86 de 32 bits.
fonte
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
).Observe que o Codepad não possui
stddef.h
. Eu removi o teste 9 devido ao codepad usando avisos como erros. Também renomei acount
variável, pois ela já estava definida por algum motivo.fonte
Que tal mudar de posição em quantidades excessivas - isso é permitido pelo padrão ou vale a pena testar?
O Padrão C especifica o comportamento do seguinte programa:
Em pelo menos um compilador que eu uso, esse código falhará, a menos que o argumento para print_string seja um "char const *". O padrão permite tal restrição?
Alguns sistemas permitem produzir indicadores para int desalinhados e outros não. Pode valer a pena testar.
fonte
<<
e>>
). C99 possui linguagem idêntica em §6.5.7-3.putch
(por que você não usou o padrãoputchar
?), Não vejo nenhum comportamento indefinido no seu programa. C89 §3.1.4 especifica que “um literal de cadeia de caracteres tem […] o tipo 'array of char'” (nota: noconst
) e que “se o programa tentar modificar um literal de cadeia de caracteres […], o comportamento será indefinido” . Que compilador é esse e como ele traduz esse programa?Para sua informação, para aqueles que precisam traduzir suas habilidades em C para Java, aqui estão algumas dicas.
Em Java, char é de 16 bits e assinado. byte é de 8 bits e assinado.
long é sempre de 64 bits, as referências podem ser de 32 ou 64 bits (se você tiver mais de um aplicativo com mais de 32 GB). As JVMs de 64 bits normalmente usam referências de 32 bits.
O deslocamento é mascarado para que i << 64 == i == i << -64, i << 63 == i << -1
ByteOrder.nativeOrder () pode ser BIG_ENDIAN ou LITTLE_ENDIAN
i = i++
nunca mudai
O tamanho das coleções e matrizes é sempre de 32 bits, independentemente de a JVM ser de 32 ou 64 bits.
char é 16 bits, curto é 16 bits, int é 32 bits e longo é 64 bits.
fonte