Recursos ocultos de C

141

Eu sei que há um padrão por trás de todas as implementações do compilador C, portanto, não deve haver recursos ocultos. Apesar disso, tenho certeza de que todos os desenvolvedores de C têm truques ocultos / secretos que usam o tempo todo.

bernardn
fonte
Seria ótimo se você / alguém editasse a “pergunta” para indicar a escolha dos melhores recursos ocultos, como nas versões C # e Perl desta pergunta.
Donal Fellows

Respostas:

62

Ponteiros de função. Você pode usar uma tabela de ponteiros de função para implementar, por exemplo, aceleradores de código de thread indireto (FORTH) rápidos ou distribuidores de código de bytes, ou para simular métodos virtuais semelhantes a OO.

Depois, existem gemas ocultas na biblioteca padrão, como qsort (), bsearch (), strpbrk (), strcspn () [as duas últimas sendo úteis para implementar uma substituição de strtok ()].

Um erro de C é que o estouro aritmético assinado é um comportamento indefinido (UB). Portanto, sempre que você vê uma expressão como x + y, ambas sendo assinadas, ela pode potencialmente estourar e causar UB.

zvrba
fonte
29
Mas se eles tivessem especificado o comportamento em excesso, isso tornaria muito lento em arquiteturas onde esse não era o comportamento normal. Sobrecarga de tempo de execução muito baixa sempre foi um objetivo de design do C, e isso significa que muitas coisas como essa são indefinidas.
Mark Baker
9
Estou muito ciente de por que o estouro é UB. Ainda é uma característica incorreta, porque o padrão deve ter pelo menos fornecido rotinas de biblioteca que possam testar o estouro aritmético (de todas as operações básicas) sem causar UB.
zvrba
2
@zvrba, "rotinas de biblioteca que podem testar o estouro aritmético (de todas as operações básicas)" se você tivesse adicionado isso, teria incorrido em um impacto significativo no desempenho de qualquer operação aritmética inteira. ===== Estudo de caso O Matlab ADICIONA especificamente o recurso de controlar o comportamento de estouro inteiro para encapsular ou saturar. E também lança uma exceção sempre que ocorre um estouro ==> Desempenho das operações inteiras do Matlab: MUITO LENTO. Minha própria conclusão: acho que o Matlab é um estudo de caso atraente que mostra por que você não deseja a verificação de estouro inteiro.
Trevor Boyd Smith
15
Eu disse que o padrão deveria ter fornecido suporte de biblioteca para verificação de estouro aritmético. Agora, como uma rotina de biblioteca pode ter um impacto no desempenho se você nunca a usa?
Zvrba 12/06/09
5
Um grande ponto negativo é que o GCC não possui um sinalizador para capturar estouros de número inteiro assinados e gerar uma exceção de tempo de execução. Embora existam sinalizadores x86 para detectar esses casos, o GCC não os utiliza. Ter esse sinalizador permitiria aos aplicativos não críticos para o desempenho (especialmente herdados) o benefício da segurança com uma revisão e refatoração mínimas ou inexistentes de código.
22630 Andrew Keeton
116

Mais um truque do compilador GCC, mas você pode fornecer dicas de indicação de ramificação para o compilador (comum no kernel do Linux)

#define likely(x)       __builtin_expect((x),1)
#define unlikely(x)     __builtin_expect((x),0)

veja: http://kerneltrap.org/node/4705

O que eu gosto sobre isso é que ele também acrescenta alguma expressividade a algumas funções.

void foo(int arg)
{
     if (unlikely(arg == 0)) {
           do_this();
           return;
     }
     do_that();
     ...
}
tonylo
fonte
2
Esse truque é legal ... :) Especialmente com as macros que você define. :)
sundar - Restabelece Monica
77
int8_t
int16_t
int32_t
uint8_t
uint16_t
uint32_t

Esse é um item opcional no padrão, mas deve ser um recurso oculto, porque as pessoas os redefinem constantemente. Uma base de código em que trabalhei (e ainda o faço, por enquanto) tem várias redefinições, todas com identificadores diferentes. Na maioria das vezes, é com macros de pré-processador:

#define INT16 short
#define INT32  long

E assim por diante. Isso me faz querer arrancar meus cabelos. Basta usar os inteiros padrão typedefs!

Ben Collins
fonte
3
Eu acho que eles são C99 ou mais. Não encontrei uma maneira portátil de garantir que elas existissem.
akauppi 25/09/08
3
Eles são uma parte opcional do C99, mas não conheço nenhum fornecedor de compilador que não implemente isso.
Ben Collins
10
stdint.h não é opcional no C99, mas seguir o padrão C99 aparentemente é para alguns fornecedores ( tosse Microsoft).
Ben Combee 22/10/08
5
@Pete, se você deseja ser anal: (1) Este tópico não tem nada a ver com qualquer produto da Microsoft. (2) Esse encadeamento nunca teve nada a ver com C ++. (3) Não existe C ++ 97.
Ben Collins
5
Ter um olhar para azillionmonkeys.com/qed/pstdint.h - um fim-de-portátil stdint.h
gnud
73

O operador de vírgula não é amplamente utilizado. Certamente pode ser abusado, mas também pode ser muito útil. Esse uso é o mais comum:

for (int i=0; i<10; i++, doSomethingElse())
{
  /* whatever */
}

Mas você pode usar esse operador em qualquer lugar. Observar:

int j = (printf("Assigning variable j\n"), getValueFromSomewhere());

Cada instrução é avaliada, mas o valor da expressão será o da última instrução avaliada.

Ben Collins
fonte
7
Em 20 anos de CI NUNCA vi isso!
22320 Martin Beckett
11
No C ++, você pode até sobrecarregá-lo.
Wouter Lievens
6
can! = deveria, é claro. O perigo de sobrecarregar é que o built-in já se aplica a tudo, incluindo void, portanto nunca falha na compilação por falta de sobrecarga disponível. Ou seja, dá ao programador muita corda.
234 Aaron Aaron
O int dentro do loop não funcionará com C: é um aprimoramento em C ++. O "," é a mesma operação que para (i = 0, j = 10; i <j; j--, i ++)?
Aif
63

inicializando a estrutura para zero

struct mystruct a = {0};

isso zerará todos os elementos da estrutura.

mike511
fonte
2
Não zera o preenchimento, se houver, no entanto.
Mikeage #
2
@ Simonn, não, ele não faz um comportamento indefinido se a estrutura contém tipos não integrais. memset com 0 na memória de um float / double ainda será zero quando você interpretar o float / double (float / double são projetados dessa maneira de propósito).
Trevor Boyd Smith
6
@ Andrew: memset/ callocdo "todos os bytes zero" (ou seja, zeros físicos), o que de fato não é definido para todos os tipos. { 0 } é garantido para intilizar tudo com valores lógicos zero adequados . Os ponteiros, por exemplo, são garantidos para obter seus valores nulos adequados, mesmo que o valor nulo na plataforma fornecida seja 0xBAADFOOD.
AnT
1
@ nvl: você obtém zero físico quando apenas define com força toda a memória ocupada pelo objeto no estado zero de todos os bits. É memsetisso que faz (com 0o segundo argumento). Você obtém zero lógico ao inicializar / atribuir 0(ou { 0 }) ao objeto no código-fonte. Esses dois tipos de zeros não produzem necessariamente o mesmo resultado. Como no exemplo com ponteiro. Quando você faz memsetum ponteiro, recebe um 0x0000ponteiro. Porém, quando você atribui 0a um ponteiro, obtém um valor nulo , que no nível físico pode ser 0xBAADF00Dou qualquer outra coisa.
AnT
3
@nvl: Bem, na prática, a diferença geralmente é apenas conceitual. Mas, em teoria, praticamente qualquer tipo pode tê-lo. Por exemplo double,. Geralmente é implementado de acordo com o padrão IEEE-754, no qual o zero lógico e o zero físico são os mesmos. Mas o IEEE-754 não é exigido pelo idioma. Portanto, pode acontecer que, quando você fizer double d = 0;(zero lógico), fisicamente alguns bits na memória ocupada por dnão sejam zero.
AnT
52

Constantes com vários caracteres:

int x = 'ABCD';

Isso define xcomo 0x41424344(ou 0x44434241, dependendo da arquitetura).

Edição: Esta técnica não é portátil, especialmente se você serializar o int. No entanto, pode ser extremamente útil criar enums de auto-documentação. por exemplo

enum state {
    stopped = 'STOP',
    running = 'RUN!',
    waiting = 'WAIT',
};

Isso torna muito mais simples se você estiver olhando para um despejo de memória bruta e precisar determinar o valor de uma enumeração sem precisar procurar.

Ferruccio
fonte
Tenho certeza de que isso não é uma construção portátil. O resultado da criação de uma constante de vários caracteres é definido pela implementação.
Mark Bessey
8
Os comentários "não portáteis" não entendem o ponto. É como criticar um programa por usar o INT_MAX apenas porque o INT_MAX "não é portátil" :) Esse recurso é o mais portátil possível. A constante de vários caracteres é um recurso extremamente útil que fornece uma maneira legível de gerar IDs inteiros exclusivos.
AnT
1
@ Chris Lutz - Tenho certeza de que a vírgula à direita remonta a K&R. É descrito na segunda edição (1988).
Ferruccio
1
@Ferruccio: Você deve estar pensando na vírgula à direita nas listas agregadas de iniciador. Quanto à vírgula à direita nas declarações de enum - é uma adição recente, C99.
28/10/09
3
Você esqueceu 'HANG' ou 'BSOD' :-)
JBRWilkinson 02/11/2009
44

Eu nunca usei campos de bits, mas eles parecem legais para coisas de nível ultra baixo.

struct cat {
    unsigned int legs:3;  // 3 bits for legs (0-4 fit in 3 bits)
    unsigned int lives:4; // 4 bits for lives (0-9 fit in 4 bits)
    // ...
};

cat make_cat()
{
    cat kitty;
    kitty.legs = 4;
    kitty.lives = 9;
    return kitty;
}

Isso significa que sizeof(cat)pode ser tão pequeno quanto sizeof(char).


Comentários incorporados de Aaron e leppie , obrigado pessoal.

Motti
fonte
A combinação de estruturas e uniões é ainda mais interessante - em sistemas embarcados ou código de driver de baixo nível. Um exemplo é quando você gosta de analisar os registros de um cartão SD, pode lê-lo usando union (1) e lê-lo usando union (2), que é uma estrutura de campos de bits.
ComSubVie
5
Os campos de bits não são portáteis - o compilador pode escolher livremente se, no seu exemplo, as pernas receberão os 3 bits mais significativos ou os 3 bits menos significativos.
Zvrba 25/09/08
3
Os campos de bits são um exemplo de onde o padrão oferece às implementações tanta liberdade na forma como são implementadas que, na prática, são quase inúteis. Se você se importa com quantos bits um valor ocupa e como é armazenado, é melhor usar máscaras de bits.
Mark Bessey
26
Os campos de bits são realmente portáteis, desde que você os trate como os elementos de estrutura que são, e não como "pedaços de números inteiros". O tamanho, não a localização, é importante em um sistema incorporado com memória limitada, pois cada bit é precioso ... mas a maioria dos codificadores de hoje é jovem demais para lembrar disso. :-)
Adam Liss
5
@ Adam: a localização pode muito bem importar em um sistema incorporado (ou em outro local), se você estiver dependendo da posição do campo de bits em seu byte. O uso de máscaras remove qualquer ambiguidade. Da mesma forma para os sindicatos.
Steve Melnikoff #
37

C possui um padrão, mas nem todos os compiladores C são totalmente compatíveis (ainda não vi nenhum compilador C99 totalmente compatível!).

Dito isso, os truques que eu prefiro são aqueles que não são óbvios e são portáveis ​​entre plataformas, pois contam com a semântica C. Eles geralmente são sobre macros ou aritmética de bits.

Por exemplo: trocando dois números inteiros não assinados sem usar uma variável temporária:

...
a ^= b ; b ^= a; a ^=b;
...

ou "estendendo C" para representar máquinas de estados finitos como:

FSM {
  STATE(x) {
    ...
    NEXTSTATE(y);
  }

  STATE(y) {
    ...
    if (x == 0) 
      NEXTSTATE(y);
    else 
      NEXTSTATE(x);
  }
}

isso pode ser alcançado com as seguintes macros:

#define FSM
#define STATE(x)      s_##x :
#define NEXTSTATE(x)  goto s_##x

Em geral, porém, eu não gosto dos truques que são inteligentes, mas tornam o código desnecessariamente complicado de ler (como o exemplo de troca) e adoro os que tornam o código mais claro e transmitem diretamente a intenção (como o exemplo do FSM) .

Remo.D
fonte
18
C suporta encadeamento, para que você possa fazer a ^ = b ^ = a ^ = b;
JO.
4
Estritamente falando, o exemplo de estado é uma marca do pré-processador, e não da linguagem C - é possível usar o primeiro sem o último.
Greg Whitfield
15
JO: na verdade, o que você sugere é um comportamento indefinido por causa das regras de ponto de sequência. Pode funcionar na maioria dos compiladores, mas não é correto ou portátil.
Evan Teran
5
Na verdade, a troca Xor poderia ser menos eficiente no caso de um registro gratuito. Qualquer otimizador decente tornaria a variável temp um registro. Dependendo da implementação (e da necessidade de suporte ao paralelismo), a troca pode realmente usar memória real em vez de um registro (que seria o mesmo).
Paul de Vrieze
27
por favor, não nunca realmente fazer isso: en.wikipedia.org/wiki/...
Christian Oudard
37

Estruturas de entrelaçamento como o dispositivo de Duff :

strncpy(to, from, count)
char *to, *from;
int count;
{
    int n = (count + 7) / 8;
    switch (count % 8) {
    case 0: do { *to = *from++;
    case 7:      *to = *from++;
    case 6:      *to = *from++;
    case 5:      *to = *from++;
    case 4:      *to = *from++;
    case 3:      *to = *from++;
    case 2:      *to = *from++;
    case 1:      *to = *from++;
               } while (--n > 0);
    }
}
ComSubVie
fonte
29
@ComSubVie, qualquer um que use o Duff's Device é um script infantil que viu o Duff's Device e pensou que seu código ficaria 1337 se usasse o Duff's Device. (1.) O dispositivo da Duff não oferece nenhum aumento de desempenho no processador moderno porque os processadores modernos têm loop zero de sobrecarga. Em outras palavras, é um pedaço de código obsoleto. (2.) Mesmo que o seu processador não ofereça loop zero de sobrecarga, provavelmente terá algo como SSE / altivec / processamento de vetores, que deixará o dispositivo do Duff envergonhado quando você usar o memcpy (). (3.) Mencionei que outro que fazer memcpy () duff's não é útil?
Trevor Boyd Smith
2
@ComSubVie, por favor cumprir o meu punho-de-morte ( en.wikipedia.org/wiki/... )
Trevor Boyd Smith
12
@ Trevor: então, apenas scripts para crianças programam 8051 e microcontroladores PIC, certo?
SF.
6
@ Trevor Boyd Smith: Enquanto o Duff's Device parece desatualizado, ainda é uma curiosidade histórica, que valida a resposta do ComSubVie. De qualquer forma, citando a Wikipedia: "Quando várias instâncias do dispositivo de Duff foram removidas do XFree86 Server na versão 4.0, houve uma melhoria notável no desempenho". ...
paercebal
2
No Symbian, uma vez avaliamos vários loops para codificação rápida de pixels; o dispositivo do duff, em montador, foi o mais rápido. Portanto, ele ainda tinha relevância nos principais núcleos de ARM dos seus smartphones hoje.
Will
33

Eu gosto muito de inicializadores designados, adicionados no C99 (e suportados no gcc por um longo tempo):

#define FOO 16
#define BAR 3

myStructType_t myStuff[] = {
    [FOO] = { foo1, foo2, foo3 },
    [BAR] = { bar1, bar2, bar3 },
    ...

A inicialização do array não depende mais da posição. Se você alterar os valores de FOO ou BAR, a inicialização do array corresponderá automaticamente ao novo valor.

DGentry
fonte
A sintaxe que o gcc suporta há muito tempo não é a mesma que a sintaxe C99 padrão.
Mark Baker
28

O C99 possui uma incrível inicialização de estrutura em qualquer ordem.

struct foo{
  int x;
  int y;
  char* name;
};

void main(){
  struct foo f = { .y = 23, .name = "awesome", .x = -38 };
}

Jason
fonte
27

estruturas e matrizes anônimas é a minha favorita. (cf. http://www.run.montefiore.ulg.ac.be/~martin/resources/kung-f00.html )

setsockopt(yourSocket, SOL_SOCKET, SO_REUSEADDR, (int[]){1}, sizeof(int));

ou

void myFunction(type* values) {
    while(*values) x=*values++;
}
myFunction((type[]){val1,val2,val3,val4,0});

pode até ser usado para instanciar listas vinculadas ...

PypeBros
fonte
3
Esse recurso geralmente é chamado de "literais compostos". Estruturas anônimas (ou sem nome) designam estruturas aninhadas que não têm nomes de membros.
calandoa
de acordo com meu GCC, "ISO C90 proíbe literais compostos".
jmtd
"A ISO C99 suporta literais compostos." "Como uma extensão, o GCC suporta literais compostos no modo C89 e em C ++" (dixit info gcc). Além disso, "como uma extensão GNU, o GCC permite a inicialização de objetos com duração de armazenamento estático por literais compostos (o que não é possível na ISO C99, porque o inicializador não é uma constante)."
PypeBros
24

O gcc possui várias extensões para o idioma C que eu gosto, que podem ser encontradas aqui . Alguns dos meus favoritos são atributos de função . Um exemplo extremamente útil é o atributo format. Isso pode ser usado se você definir uma função personalizada que usa uma sequência de caracteres no formato printf. Se você habilitar esse atributo de função, o gcc fará verificações nos seus argumentos para garantir que a sequência e os argumentos do formato sejam correspondentes e gerará avisos ou erros, conforme apropriado.

int my_printf (void *my_object, const char *my_format, ...)
            __attribute__ ((format (printf, 2, 3)));
Russell Bryant
fonte
24

o recurso (oculto) que me "chocou" quando vi pela primeira vez é sobre printf. esse recurso permite usar variáveis ​​para formatar os especificadores de formato. procure o código, você verá melhor:

#include <stdio.h>

int main() {
    int a = 3;
    float b = 6.412355;
    printf("%.*f\n",a,b);
    return 0;
}

o caractere * atinge esse efeito.

Kolistivra
fonte
24

Bem ... acho que um dos pontos fortes da linguagem C é a portabilidade e a padronização; portanto, sempre que encontro algum "truque oculto" na implementação que estou usando atualmente, tento não usá-lo porque tento manter meu Código C o mais padrão e portátil possível.

Giacomo Degli Esposti
fonte
Mas, na realidade, com que frequência você precisa compilar seu código com outro compilador?
Joe D
3
@ Joe D se for um projeto multi-plataforma como o Windows / OSX / Linux, provavelmente um pouco, e também não é diferente arco tais como x86 vs x86_64 e etc ...
Pharaun
@ JoeO A menos que você esteja em um projeto muito limitado, feliz em se casar com um fornecedor de compilador. Você pode evitar realmente ter que trocar de compilador, mas deseja manter essa opção em aberto. Com sistemas embarcados, você nem sempre tem escolha. AHS, ASS.
XTL
19

Asserções em tempo de compilação, como já discutido aqui .

//--- size of static_assertion array is negative if condition is not met
#define STATIC_ASSERT(condition) \
    typedef struct { \
        char static_assertion[condition ? 1 : -1]; \
    } static_assertion_t

//--- ensure structure fits in 
STATIC_ASSERT(sizeof(mystruct_t) <= 4096);
philant
fonte
16

Concatenação de cadeia constante

Fiquei bastante surpreso por não ter visto tudo nas respostas, pois todos os compiladores que conheço o apóiam, mas muitos programadores parecem ignorá-lo. Às vezes, é realmente útil e não apenas ao escrever macros.

Caso de uso que tenho no meu código atual: Eu tenho um #define PATH "/some/path/"em um arquivo de configuração (realmente é definido pelo makefile). Agora eu quero construir o caminho completo, incluindo nomes de arquivos para abrir recursos. Apenas vai para:

fd = open(PATH "/file", flags);

Em vez do horrível, mas muito comum:

char buffer[256];
snprintf(buffer, 256, "%s/file", PATH);
fd = open(buffer, flags);

Observe que a solução horrível comum é:

  • três vezes mais
  • muito menos fácil de ler
  • muito mais devagar
  • menos poderoso nele definido como um limite arbitrário de tamanho do buffer (mas você teria que usar um código ainda mais longo para evitar isso sem contatenação de cadeias constantes).
  • use mais espaço na pilha
kriss
fonte
1
Também é útil dividir uma constante de string em várias linhas de origem sem usar `\` sujo.
dólmen
15

Bem, eu nunca o usei e não tenho certeza se recomendaria a alguém, mas acho que essa pergunta seria incompleta sem uma menção ao truque co-rotineiro de Simon Tatham .

Mark Baker
fonte
12

Ao inicializar matrizes ou enumerações, você pode colocar uma vírgula após o último item na lista de inicializadores. por exemplo:

int x[] = { 1, 2, 3, };

enum foo { bar, baz, boom, };

Isso foi feito para que, se você estiver gerando código automaticamente, não precise se preocupar em eliminar a última vírgula.

Ferruccio
fonte
Isso também é importante em um ambiente de vários desenvolvedores, onde, por exemplo, Eric adiciona "baz" e George adiciona "boom". Se Eric decidir retirar seu código para a próxima construção do projeto, ele ainda será compilado com a alteração de George. Muito importante para o controle do código fonte de várias filiais e sobreposições de cronogramas de desenvolvimento.
Harold Bamford
Enums podem ser C99. Inicializadores de matriz e vírgula à direita são K&R.
Ferruccio
As enumerações simples estavam em c89, AFAIK. Pelo menos eles já existem há séculos.
XTL
12

A atribuição de estruturas é legal. Muitas pessoas parecem não perceber que as estruturas também são valores e podem ser atribuídas ao redor, não há necessidade de usarmemcpy() , quando uma tarefa simples faz o truque.

Por exemplo, considere alguma biblioteca gráfica 2D imaginária, ela pode definir um tipo para representar uma coordenada da tela (inteira):

typedef struct {
   int x;
   int y;
} Point;

Agora, você faz coisas que podem parecer "erradas", como escrever uma função que cria um ponto inicializado a partir dos argumentos da função e a retorna da seguinte maneira:

Point point_new(int x, int y)
{
  Point p;
  p.x = x;
  p.y = y;
  return p;
}

Isso é seguro, desde que, é claro, o valor de retorno seja copiado por valor usando a atribuição de estrutura:

Point origin;
origin = point_new(0, 0);

Dessa forma, você pode escrever um código ish bastante limpo e orientado a objetos, tudo no padrão C.

desenrolam
fonte
4
Obviamente, há implicações de desempenho na passagem de grandes estruturas dessa maneira; geralmente é útil (e é de fato algo que muitas pessoas não percebem que você pode fazer), mas é necessário considerar se passar os ponteiros é melhor.
Mark Baker
1
Claro, pode haver. Também é possível para o compilador detectar o uso e otimizá-lo.
Desassociar
Cuidado se algum dos elementos for ponteiro, pois você estará copiando os ponteiros, não o conteúdo. Obviamente, o mesmo acontece se você usar o memcpy ().
Adam Liss
O compilador não pode otimizar essa passagem de conversão de valor por referência, a menos que possa fazer otimizações globais.
Blaisorblade 19/01/09
Provavelmente vale a pena notar que, em C ++, o padrão permite especificamente otimizar a cópia (o padrão precisa permitir que os compiladores a implementem porque significa que o construtor de cópias que pode ter efeitos colaterais pode não ser chamado) e, como a maioria dos compiladores de C ++ também são compiladores C, há uma boa chance do seu compilador fazer essa otimização.
Joseph Garvin
10

Indexação de vetor estranho:

int v[100]; int index = 10; 
/* v[index] it's the same thing as index[v] */
INS
fonte
4
É ainda melhor ... char c = 2 ["Olá"]; (c == 'l' depois disso).
yrp 25/09/08
5
Não é tão estranho quando você considera que v [índice] == * (v + index) eo índice [v] == * (índice de + v)
Ferruccio
17
Por favor, diga-me que você realmente não usa isso "o tempo todo", como a pergunta!
Tryke 25/09/08
9

Os compiladores C implementam um dos vários padrões. No entanto, ter um padrão não significa que todos os aspectos da linguagem sejam definidos. Dispositivo de Duff , por exemplo, é um recurso 'oculto' favorito que se tornou tão popular que os compiladores modernos têm código de reconhecimento de finalidade especial para garantir que as técnicas de otimização não obtenham o efeito desejado desse padrão frequentemente usado.

Em geral, os recursos ocultos ou os truques de linguagem são desencorajados, enquanto você está rodando na borda dos padrões C, que seu compilador usa. Muitos desses truques não funcionam de um compilador para outro e, geralmente, esses tipos de recursos falham de uma versão de um conjunto de compiladores de um determinado fabricante para outra versão.

Vários truques que quebraram o código C incluem:

  1. Confiando em como o compilador distribui estruturas na memória.
  2. Pressupostos sobre endianidade de números inteiros / flutuantes.
  3. Pressupostos sobre a função ABIs.
  4. Pressupostos sobre a direção em que os quadros de pilha crescem.
  5. Pressupostos sobre a ordem de execução nas instruções.
  6. Pressupostos sobre a ordem de execução de instruções em argumentos de função.
  7. Pressupostos sobre o tamanho do bit ou a precisão dos tipos short, int, long, float e double.

Outros problemas e questões que surgem sempre que os programadores fazem suposições sobre os modelos de execução especificados na maioria dos padrões C como comportamento 'dependente do compilador'.

Kevin S.
fonte
Para resolver a maioria deles, torne essas suposições dependentes das características da sua plataforma e descreva cada plataforma em seu próprio cabeçalho. A execução da ordem é uma exceção - nunca confie nisso; nas outras idéias, cada plataforma precisa ter uma decisão confiável.
Blaisorblade 19/01/09
2
@Blaisorblade, Melhor ainda, use asserções em tempo de compilação para documentar suas suposições de uma maneira que fará com que a compilação falhe em uma plataforma onde elas são violadas.
RBerteig
Eu acho que é preciso combinar os dois, para que seu código funcione em várias plataformas (que era a intenção original) e, se as macros de recursos estiverem definidas da maneira errada, as asserções em tempo de compilação o entenderão. Não tenho certeza se, digamos, a suposição da ABI da função é verificável como asserções em tempo de compilação, mas deve ser possível para a maioria das outras (válidas) (exceto ordem de execução ;-)).
Blaisorblade
As verificações da função ABI devem ser tratadas por um conjunto de testes.
Dolmen
9

Ao usar o sscanf, você pode usar% n para descobrir onde deve continuar lendo:

sscanf ( string, "%d%n", &number, &length );
string += length;

Aparentemente, você não pode adicionar outra resposta, então incluirei uma segunda aqui, você pode usar "&&" e "||" como condicionais:

#include <stdio.h>
#include <stdlib.h>

int main()
{
   1 || puts("Hello\n");
   0 || puts("Hi\n");
   1 && puts("ROFL\n");
   0 && puts("LOL\n");

   exit( 0 );
}

Este código produzirá:

Oi
ROFL
onemasse
fonte
8

usar INT (3) para definir o ponto de interrupção no código é o meu favorito de todos os tempos

Dror Helper
fonte
3
Eu não acho que seja portátil. Ele funcionará no x86, mas e outras plataformas?
Cristian Ciupitu 25/09/08
1
Eu não tenho idéia - Você deve postar uma pergunta sobre isso
Dror Helper
2
É uma boa técnica e é específica para o X86 (embora provavelmente haja técnicas semelhantes em outras plataformas). No entanto, esse não é um recurso do C. Depende de extensões C não padrão ou de chamadas de biblioteca.
Ferruccio
1
No GCC, há __builtin_trap e, para MSVC __debugbreak, que funcionará em qualquer arquitetura suportada.
Axel Gneiting
8

Meu recurso "oculto" favorito de C é o uso de% n no printf para gravar de volta na pilha. Normalmente printf exibe os valores dos parâmetros da pilha com base na string de formato, mas% n pode gravá-los de volta.

Confira a seção 3.4.2 aqui . Pode levar a muitas vulnerabilidades desagradáveis.

Sridhar Iyer
fonte
o link não está mais funcionando, de fato, o site em si parece não estar funcionando. Você pode fornecer outro link?
thequark
@ thequark: Qualquer artigo sobre "vulnerabilidades de cadeias de formato" terá algumas informações .. (por exemplo, crypto.stanford.edu/cs155/papers/formatstring-1.2.pdf ) .. No entanto, devido à natureza do campo, a segurança os sites são um pouco esquisitos e é difícil encontrar artigos acadêmicos reais (com a implementação).
precisa saber é o seguinte
8

Verificação de suposições em tempo de compilação usando enumerações: exemplo estúpido, mas pode ser realmente útil para bibliotecas com constantes configuráveis ​​em tempo de compilação.

#define D 1
#define DD 2

enum CompileTimeCheck
{
    MAKE_SURE_DD_IS_TWICE_D = 1/(2*(D) == (DD)),
    MAKE_SURE_DD_IS_POW2    = 1/((((DD) - 1) & (DD)) == 0)
};
SC Madsen
fonte
2
+1 limpo. Eu costumava usar a macro CompilerAssert da Microsoft, mas a sua também não é ruim. ( #define CompilerAssert(exp) extern char _CompilerAssert[(exp)?1:-1])
Patrick Schlüter
1
Eu gosto do método de enumeração. A abordagem que eu usei antes aproveitou a eliminação do código morto: "if (something_bad) {void BLORG_IS_WOOZLED (void); BLORG_IS_WOOZLED ();}" que não tinha erro até o tempo do link, embora oferecesse a vantagem de permitir que o programador sabe por mensagem de erro que o blorg foi cortejado.
Supercat
8

O Gcc (c) possui alguns recursos divertidos que você pode ativar, como declarações de funções aninhadas e a forma a?: B do operador?: Que retorna a se a não for falso.

Alex Brown
fonte
8

Descobri recentemente 0 campos de bits.

struct {
  int    a:3;
  int    b:2;
  int     :0;
  int    c:4;
  int    d:3;
};

que dará um layout de

000aaabb 0ccccddd

em vez de sem o: 0;

0000aaab bccccddd

O campo de largura 0 indica que os seguintes campos de bits devem ser definidos na próxima entidade atômica ( char)

tristopia
fonte
7

Macros de argumento variável no estilo C99, também conhecido como

#define ERR(name, fmt, ...)   fprintf(stderr, "ERROR " #name ": " fmt "\n", \
                                  __VAR_ARGS__)

que seria usado como

ERR(errCantOpen, "File %s cannot be opened", filename);

Aqui eu também uso o operador stringize e a concatentação constante de strings, outros recursos que realmente gosto.

Ben Combee
fonte
Você tem um 'R' extra em VA_ARGS .
Blaisorblade 19/01/09
6

Variáveis ​​automáticas de tamanho variável também são úteis em alguns casos. Estes foram adicionados no nC99 e são suportados no gcc há muito tempo.

void foo(uint32_t extraPadding) {
    uint8_t commBuffer[sizeof(myProtocol_t) + extraPadding];

Você acaba com um buffer na pilha, com espaço para o cabeçalho de protocolo de tamanho fixo mais dados de tamanho variável. Você pode obter o mesmo efeito com alloca (), mas essa sintaxe é mais compacta.

Você deve garantir que extraPadding seja um valor razoável antes de chamar essa rotina, ou você acaba explodindo a pilha. Você precisaria verificar os argumentos antes de chamar o malloc ou qualquer outra técnica de alocação de memória, portanto isso não é realmente incomum.

DGentry
fonte
Isso também funcionará corretamente se um byte / char não tiver exatamente 8 bits de largura na plataforma de destino? Eu sei, esses casos são raros, mas ainda assim ... :)
Stephan202