Sintaxe C - C syntax

A sintaxe da linguagem de programação C é o conjunto de regras que regem a escrita de software na linguagem C . Ele é projetado para permitir programas que são extremamente concisos, têm um relacionamento próximo com o código do objeto resultante e, ainda assim, fornecem abstração de dados de nível relativamente alto . C foi a primeira linguagem de alto nível amplamente bem-sucedida para o desenvolvimento de sistemas operacionais portáteis .

A sintaxe C usa o princípio maximal munch .

Estruturas de dados

Tipos de dados primitivos

A linguagem C representa números em três formas: integral , real e complexo . Essa distinção reflete distinções semelhantes na arquitetura do conjunto de instruções da maioria das unidades centrais de processamento . Os tipos de dados integrais armazenam números no conjunto de inteiros , enquanto os números reais e complexos representam os números (ou pares de números) no conjunto de números reais na forma de ponto flutuante .

Todos os tipos inteiros C têm signede unsignedvariantes. Se signedou unsignednão for especificado explicitamente, na maioria das circunstâncias signedé assumido. No entanto, por razões históricas, planície charé um tipo distinto de ambos signed chare unsigned char. Pode ser um tipo assinado ou um tipo não assinado, dependendo do compilador e do conjunto de caracteres (C garante que os membros do conjunto de caracteres básicos C têm valores positivos). Além disso, os tipos de campo de bits especificados como simples intpodem ser assinados ou não, dependendo do compilador.

Tipos inteiros

Os tipos inteiros de C vêm em diferentes tamanhos fixos, capazes de representar vários intervalos de números. O tipo charocupa exatamente um byte (a menor unidade de armazenamento endereçável), que normalmente tem 8 bits de largura. (Embora charpossa representar qualquer um dos caracteres "básicos" do C, um tipo mais amplo pode ser necessário para conjuntos de caracteres internacionais.) A maioria dos tipos inteiros tem variedades com e sem sinal , designadas pelas palavras signed- unsignedchave e . Tipos inteiros assinados pode usar um complemento de dois , complemento para um , ou sign-and-magnitude representação . Em muitos casos, existem várias maneiras equivalentes de designar o tipo; por exemplo, signed short inte shortsão sinônimos.

A representação de alguns tipos pode incluir bits de "preenchimento" não utilizados, que ocupam espaço de armazenamento, mas não são incluídos na largura. A tabela a seguir fornece uma lista completa dos tipos de inteiros padrão e suas larguras mínimas permitidas (incluindo qualquer bit de sinal).

Especificações para tipos inteiros padrão
Forma mais curta de especificador Largura mínima (bits)
_Bool 1
char 8
signed char 8
unsigned char 8
short 16
unsigned short 16
int 16
unsigned int 16
long 32
unsigned long 32
long long 64
unsigned long long 64

O chartipo é distinto de ambos signed chare unsigned char, mas tem a garantia de ter a mesma representação de um deles. Os tipos _Boole long longsão padronizados desde 1999 e podem não ser suportados por compiladores C mais antigos. O tipo _Boolgeralmente é acessado por meio do typedefnome booldefinido pelo cabeçalho padrão stdbool.h .

Em geral, as larguras e o esquema de representação implementado para qualquer plataforma são escolhidos com base na arquitetura da máquina, com alguma consideração dada à facilidade de importação do código-fonte desenvolvido para outras plataformas. A largura do inttipo varia especialmente amplamente entre as implementações C; frequentemente corresponde ao tamanho de palavra mais "natural" para a plataforma específica. O cabeçalho padrão limits.h define macros para os valores mínimo e máximo representáveis ​​dos tipos inteiros padrão conforme implementados em qualquer plataforma específica.

Além dos tipos inteiros padrão, pode haver outros tipos inteiros "estendidos", que podem ser usados ​​para typedefs em cabeçalhos padrão. Para especificações mais precisas de largura, os programadores podem e devem usar typedefs do cabeçalho padrão stdint.h .

Constantes inteiras podem ser especificadas no código-fonte de várias maneiras. Os valores numéricos podem ser especificados como decimais (exemplo :)1022 , octal com zero (0) como prefixo ( 01776) ou hexadecimal com 0x (zero x) como prefixo ( 0x3FE). Um caractere entre aspas simples (exemplo 'R':), chamado de "constante de caractere", representa o valor desse caractere no conjunto de caracteres de execução, com tipo int. Exceto para constantes de caractere, o tipo de uma constante inteira é determinado pela largura necessária para representar o valor especificado, mas é sempre pelo menos tão largo quanto int. Isso pode ser sobrescrito anexando um modificador de comprimento e / ou assinatura explícito; por exemplo, 12lutem tipo unsigned long. Não há constantes inteiras negativas, mas o mesmo efeito pode frequentemente ser obtido usando um operador de negação unário "-".

Tipo enumerado

O tipo enumerado em C, especificado com a enumpalavra - chave, e frequentemente chamado apenas de "enum" (geralmente pronunciado ee'-num /ˌi.nʌm/ ou ee'-noom /ˌi.nuːm/), é um tipo projetado para representar valores através de uma série de constantes nomeadas. Cada uma das constantes enumeradas tem um tipo int. Cada enumtipo em si é compatível com charum tipo inteiro assinado ou não assinado, mas cada implementação define suas próprias regras para escolher um tipo.

Alguns compiladores avisam se um objeto com tipo enumerado é atribuído a um valor que não é uma de suas constantes. No entanto, esse objeto pode receber quaisquer valores no intervalo de seu tipo compatível e as enumconstantes podem ser usadas em qualquer lugar em que um inteiro seja esperado. Por esse motivo, os enumvalores costumam ser usados ​​no lugar das #definediretivas do pré-processador para criar constantes nomeadas. Essas constantes são geralmente mais seguras de usar do que macros, uma vez que residem em um namespace de identificador específico.

Um tipo enumerado é declarado com o enumespecificador e um nome opcional (ou tag ) para o enum, seguido por uma lista de uma ou mais constantes contidas entre chaves e separadas por vírgulas, e uma lista opcional de nomes de variáveis. As referências subsequentes a um tipo enumerado específico usam a enumpalavra - chave e o nome do enum. Por padrão, a primeira constante em uma enumeração recebe o valor zero e cada valor subsequente é incrementado em um em relação à constante anterior. Valores específicos também podem ser atribuídos a constantes na declaração, e quaisquer constantes subsequentes sem valores específicos receberão valores incrementados desse ponto em diante. Por exemplo, considere a seguinte declaração:

enum colors { RED, GREEN, BLUE = 5, YELLOW } paint_color;

Isso declara o enum colorstipo; as intconstantes RED(cujo valor é 0), GREEN(cujo valor é um maior que RED, 1), BLUE(cujo valor é o valor dado, 5) e YELLOW(cujo valor é um maior que BLUE, 6); e a enum colorsvariável paint_color. As constantes podem ser usadas fora do contexto do enum (onde qualquer valor inteiro é permitido) e valores diferentes das constantes podem ser atribuídos a paint_color, ou qualquer outra variável do tipo enum colors.

Tipos de ponto flutuante

A forma de ponto flutuante é usada para representar números com um componente fracionário. Eles, entretanto, não representam exatamente a maioria dos números racionais; eles são, em vez disso, uma aproximação próxima. Existem três tipos de valores reais, denotados por seus especificadores: precisão simples ( float), precisão dupla ( double) e precisão estendida dupla ( long double). Cada um deles pode representar valores em uma forma diferente, geralmente um dos formatos de ponto flutuante IEEE .

Tipos de vírgula flutuante
Especificadores de tipo Precisão (dígitos decimais) Faixa de expoente
Mínimo IEEE 754 Mínimo IEEE 754
float 6 7,2 (24 bits) ± 37 ± 38 (8 bits)
double 10 15,9 (53 bits) ± 37 ± 307 (11 bits)
long double 10 34,0 (113 bits) ± 37 ± 4931 (15 bits)

As constantes de vírgula flutuante podem ser escritas em notação decimal , por exemplo 1.23. A notação científica decimal pode ser usada adicionando eou Eseguida por um expoente decimal, também conhecido como notação E , por exemplo 1.23e2(que tem o valor 1,23 × 10 2 = 123,0). É necessário um ponto decimal ou um expoente (caso contrário, o número é analisado como uma constante inteira). As constantes hexadecimais de ponto flutuante seguem regras semelhantes, exceto que devem ser prefixadas por 0xe usar pou Ppara especificar um expoente binário, por exemplo 0xAp-2(que tem o valor 2,5, uma vez que A h × 2 −2 = 10 × 2 −2 = 10 ÷ 4 ) As constantes de ponto flutuante decimal e hexadecimal podem ser sufixadas por fou Fpara indicar uma constante do tipo float, por l(letra l) ou Lpara indicar o tipo long double, ou deixadas sem sufixo para uma doubleconstante.

O arquivo de cabeçalho padrão float.hdefine os valores mínimos e máximos de tipos de ponto flutuante da implementação float, doublee long double. Ele também define outros limites que são relevantes para o processamento de números de ponto flutuante.

Especificadores de classe de armazenamento

Cada objeto possui uma classe de armazenamento. Isso especifica basicamente a duração do armazenamento , que pode ser estática (padrão para global), automática (padrão para local) ou dinâmica (alocada), junto com outros recursos (ligação e dica de registro).

Classes de armazenamento
Especificadores Vida Escopo Inicializador padrão
auto Bloco (pilha) Quadra Não inicializado
register Bloco (pilha ou registro de CPU) Quadra Não inicializado
static Programa Bloco ou unidade de compilação Zero
extern Programa Global (programa completo) Zero
(nenhum) 1 Dinâmico (heap) Não inicializado (inicializado se 0estiver usando calloc())
1 Alocado e desalocado usando as funções da biblioteca malloc()e free().

As variáveis ​​declaradas em um bloco por padrão têm armazenamento automático, assim como aquelas declaradas explicitamente com os especificadores de classe de armazenamento autoou register. Os especificadores autoe registersó podem ser usados ​​em funções e declarações de argumentos de funções; como tal, o autoespecificador é sempre redundante. Objetos declarados fora de todos os blocos e aqueles declarados explicitamente com o staticespecificador de classe de armazenamento têm duração de armazenamento estático. Variáveis ​​estáticas são inicializadas com zero por padrão pelo compilador .

Objetos com armazenamento automático são locais para o bloco em que foram declarados e são descartados quando o bloco é encerrado. Além disso, os objetos declarados com a registerclasse de armazenamento podem receber prioridade mais alta do compilador para acesso aos registradores ; embora o compilador possa escolher não armazenar nenhum deles em um registrador. Objetos com esta classe de armazenamento não podem ser usados ​​com o &operador unário address-of ( ). Objetos com armazenamento estático persistem por toda a duração do programa. Dessa forma, o mesmo objeto pode ser acessado por uma função em várias chamadas. Objetos com duração de armazenamento alocado são criados e destruídos explicitamente com malloc, freee funções relacionadas.

O externespecificador de classe de armazenamento indica que o armazenamento de um objeto foi definido em outro lugar. Quando usado dentro de um bloco, indica que o armazenamento foi definido por uma declaração fora desse bloco. Quando usado fora de todos os blocos, indica que o armazenamento foi definido fora da unidade de compilação. O externespecificador de classe de armazenamento é redundante quando usado em uma declaração de função. Indica que a função declarada foi definida fora da unidade de compilação.

Observe que os especificadores de armazenamento se aplicam apenas a funções e objetos; outras coisas, como declarações de tipo e enum, são privadas para a unidade de compilação em que aparecem. Os tipos, por outro lado, têm qualificadores (veja abaixo).

Qualificadores de tipo

Os tipos podem ser qualificados para indicar propriedades especiais de seus dados. O qualificador de tipo constindica que um valor não muda depois de inicializado. A tentativa de modificar um constvalor qualificado produz um comportamento indefinido, portanto, alguns compiladores C os armazenam em rodata ou (para sistemas embarcados) em memória somente leitura (ROM). O qualificador de tipo volatileindica a um compilador de otimização que ele não pode remover leituras ou gravações aparentemente redundantes, pois o valor pode mudar mesmo que não tenha sido modificado por qualquer expressão ou instrução, ou várias gravações podem ser necessárias, como para I mapeado em memória / S .

Tipos incompletos

Um tipo incompleto é uma estrutura ou tipo de união cujos membros ainda não foram especificados, um tipo de matriz cuja dimensão ainda não foi especificada ou o voidtipo (o voidtipo não pode ser concluído). Esse tipo não pode ser instanciado (seu tamanho não é conhecido), nem seus membros podem ser acessados ​​(eles também são desconhecidos); no entanto, o tipo de ponteiro derivado pode ser usado (mas não desreferenciado).

Eles são freqüentemente usados ​​com ponteiros, como declarações de encaminhamento ou externas. Por exemplo, o código pode declarar um tipo incompleto como este:

struct thing *pt;

Isso é declarado ptcomo um ponteiro para struct thing e o tipo incompleto struct thing. Ponteiros para dados sempre têm a mesma largura de byte, independentemente de para onde eles apontam, portanto, essa instrução é válida por si só (contanto que ptnão seja desreferenciada). O tipo incompleto pode ser completado posteriormente no mesmo escopo, redeclarando-o:

struct thing {
    int num;
}; /* thing struct type is now completed */

Tipos incompletos são usados ​​para implementar estruturas recursivas ; o corpo da declaração de tipo pode ser adiado para mais tarde na unidade de tradução:

typedef struct Bert Bert;
typedef struct Wilma Wilma;

struct Bert {
    Wilma *wilma;
};

struct Wilma {
    Bert *bert;
};

Tipos incompletos também são usados ​​para ocultar dados ; o tipo incompleto é definido em um arquivo de cabeçalho e o corpo apenas no arquivo de origem relevante.

Ponteiros

Nas declarações, o modificador de asterisco ( *) especifica um tipo de ponteiro. Por exemplo, onde o especificador intse refere ao tipo inteiro, o especificador int*se refere ao tipo "ponteiro para inteiro". Os valores do ponteiro associam duas informações: um endereço de memória e um tipo de dados. A linha de código a seguir declara uma variável de ponteiro para inteiro chamada ptr :

int *ptr;

Referenciando

Quando um ponteiro não estático é declarado, ele possui um valor não especificado associado a ele. O endereço associado a tal ponteiro deve ser alterado por atribuição antes de usá-lo. No exemplo a seguir, ptr é definido de forma que aponte para os dados associados à variável a :

int a = 0;
int *ptr = &a;

Para fazer isso, o operador "endereço de" (unário &) é usado. Ele produz a localização da memória do objeto de dados que se segue.

Desreferenciamento

Os dados apontados podem ser acessados ​​por meio de um valor de ponteiro. No exemplo a seguir, a variável inteira b é definida com o valor da variável inteira a , que é 10:

int a=10;
int *p;
p = &a;
int b = *p;

Para realizar essa tarefa, o operador de desreferência unário , denotado por um asterisco (*), é usado. Ele retorna os dados para os quais seu operando - que deve ser do tipo ponteiro - aponta. Assim, a expressão * p denota o mesmo valor que a . Cancelar a referência de um ponteiro nulo é ilegal.

Arrays

Definição de matriz

Arrays são usados ​​em C para representar estruturas de elementos consecutivos do mesmo tipo. A definição de uma matriz (tamanho fixo) tem a seguinte sintaxe:

int array[100];

que define um array denominado array para conter 100 valores do tipo primitivo int. Se declarada dentro de uma função, a dimensão da matriz também pode ser uma expressão não constante, caso em que a memória para o número especificado de elementos será alocada. Na maioria dos contextos de uso posterior, uma menção ao array variável é convertida em um ponteiro para o primeiro item do array. O sizeofoperador é uma exceção: sizeof arrayproduz o tamanho de todo o array (ou seja, 100 vezes o tamanho de um inte sizeof(array) / sizeof(int)retornará 100). Outra exceção é o operador & (endereço de), que produz um ponteiro para toda a matriz, por exemplo

int (*ptr_to_array)[100] = &array;

Acessando elementos

O principal recurso para acessar os valores dos elementos de uma matriz é o operador subscrito da matriz. Para acessar o elemento i- indexado da matriz , a sintaxe seria array[i], que se refere ao valor armazenado nesse elemento da matriz.

A numeração do subscrito da matriz começa em 0 (consulte Indexação baseada em zero ). O maior subscrito de array permitido é, portanto, igual ao número de elementos no array menos 1. Para ilustrar isso, considere um array a declarado como tendo 10 elementos; o primeiro elemento seria a[0]e o último elemento seria a[9].

C não fornece nenhum recurso para verificação automática de limites para uso de array. Embora logicamente o último subscrito em uma matriz de 10 elementos seja 9, os subscritos 10, 11 e assim por diante podem ser especificados acidentalmente, com resultados indefinidos.

Como as matrizes e ponteiros são intercambiáveis, os endereços de cada um dos elementos da matriz podem ser expressos em aritmética de ponteiro equivalente . A tabela a seguir ilustra os dois métodos para a matriz existente:

Array subscripts vs. ponteiro aritmético
Elemento Primeiro Segundo Terceiro n º
Subscrito de matriz array[0] array[1] array[2] array[n - 1]
Ponteiro não referenciado *array *(array + 1) *(array + 2) *(array + n - 1)

Como a expressão a[i]é semanticamente equivalente a *(a+i), que por sua vez é equivalente a *(i+a), a expressão também pode ser escrita como i[a], embora essa forma seja raramente usada.

Matrizes de comprimento variável

Arrays de comprimento variável padronizados C99 (VLAs) dentro do escopo do bloco. Essas variáveis ​​de matriz são alocadas com base no valor de um valor inteiro em tempo de execução na entrada em um bloco e são desalocadas no final do bloco. A partir do C11, esse recurso não precisa mais ser implementado pelo compilador.

int n = ...;
int a[n];
a[3] = 10;

Essa sintaxe produz um array cujo tamanho é fixo até o final do bloco.

Matrizes dinâmicas

Arrays que podem ser redimensionados dinamicamente podem ser produzidos com a ajuda da biblioteca C padrão . A mallocfunção fornece um método simples para alocar memória. Leva um parâmetro: a quantidade de memória a ser alocada em bytes. Após a alocação bem-sucedida, mallocretorna um voidvalor de ponteiro genérico ( ), apontando para o início do espaço alocado. O valor do ponteiro retornado é convertido em um tipo apropriado implicitamente por atribuição. Se a alocação não pôde ser concluída, mallocretorna um ponteiro nulo . O segmento a seguir é, portanto, semelhante em função à declaração desejada acima:

#include <stdlib.h> /* declares malloc */
...
int *a = malloc(n * sizeof *a);
a[3] = 10;

O resultado é um "ponteiro para int" variável ( a ) que aponta para o primeiro de nint objetos contíguos ; devido à equivalência de ponteiro de matriz, isso pode ser usado no lugar de um nome de matriz real, conforme mostrado na última linha. A vantagem de usar essa alocação dinâmica é que a quantidade de memória alocada a ela pode ser limitada ao que é realmente necessário em tempo de execução e isso pode ser alterado conforme necessário (usando a função de biblioteca padrão realloc).

Quando a memória alocada dinamicamente não é mais necessária, ela deve ser liberada de volta para o sistema de tempo de execução. Isso é feito com uma chamada para a freefunção. Leva um único parâmetro: um ponteiro para a memória alocada anteriormente. Este é o valor que foi retornado por uma chamada anterior para malloc.

Como medida de segurança, alguns programadores definem a variável de ponteiro para NULL:

free(a);
a = NULL;

Isso garante que outras tentativas de cancelar a referência do ponteiro, na maioria dos sistemas, travarão o programa. Se isso não for feito, a variável se torna um ponteiro pendente que pode levar a um bug de uso após livre. No entanto, se o ponteiro for uma variável local, defini-lo como NULLnão impede que o programa use outras cópias do ponteiro. Os bugs locais livres de uso são geralmente fáceis de serem reconhecidos pelos analisadores estáticos . Portanto, essa abordagem é menos útil para ponteiros locais e é mais frequentemente usada com ponteiros armazenados em estruturas de longa vida. Em geral, porém, definir ponteiros para NULLé uma boa prática, pois permite que um programador NULLverifique os ponteiros antes de cancelar a referência, ajudando assim a evitar travamentos.

Recordando o exemplo da matriz, também se pode criar uma matriz de tamanho fixo por meio da alocação dinâmica:

int (*a)[100] = malloc(sizeof *a);

... O que produz um ponteiro para array.

O acesso ao ponteiro para a matriz pode ser feito de duas maneiras:

(*a)[index];

index[*a];

A iteração também pode ser feita de duas maneiras:

for (int i = 0; i < 100; i++)
    (*a)[i];

for (int *i = a[0]; i < a[1]; i++)
    *i;

O benefício de usar o segundo exemplo é que o limite numérico do primeiro exemplo não é necessário, o que significa que o ponteiro para a matriz pode ser de qualquer tamanho e o segundo exemplo pode ser executado sem nenhuma modificação.

Matrizes multidimensionais

Além disso, C oferece suporte a matrizes de várias dimensões, que são armazenadas na ordem de linha principal . Tecnicamente, os arrays multidimensionais C são apenas arrays unidimensionais cujos elementos são arrays. A sintaxe para declarar matrizes multidimensionais é a seguinte:

int array2d[ROWS][COLUMNS];

onde ROWS e COLUMNS são constantes. Isso define uma matriz bidimensional. Lendo os subscritos da esquerda para a direita, array2d é um array de ROWS de comprimento , cada elemento dos quais é um array de COLUMNS inteiros.

Para acessar um elemento inteiro neste array multidimensional, seria usado

array2d[4][3]

Novamente, lendo da esquerda para a direita, acessa a 5ª linha e o 4º elemento dessa linha. A expressão array2d[4]é uma matriz, que estamos então subscrevendo com [3] para acessar o quarto inteiro.

Array subscripts vs. ponteiro aritmético
Elemento Primeiro Segunda linha, segunda coluna i ésima linha, j ésima coluna
Subscrito de matriz array[0][0] array[1][1] array[i - 1][j - 1]
Ponteiro não referenciado *(*(array + 0) + 0) *(*(array + 1) + 1) *(*(array + i - 1) + j - 1)

Matrizes de dimensões superiores podem ser declaradas de maneira semelhante.

Uma matriz multidimensional não deve ser confundida com uma matriz de referências a matrizes (também conhecida como vetores Iliffe ou, às vezes, uma matriz de matrizes ). O primeiro é sempre retangular (todos os subarrays devem ter o mesmo tamanho) e ocupa uma região contígua da memória. O último é uma matriz unidimensional de ponteiros, cada um dos quais pode apontar para o primeiro elemento de uma submatriz em um local diferente na memória, e as submatrizes não precisam ser do mesmo tamanho. O último pode ser criado por múltiplos usos de malloc.

Cordas

Em C, literais de string são colocados entre aspas duplas ( "), por exemplo, "Hello world!"e são compilados em uma matriz dos charvalores especificados com um código de caractere de terminação nulo adicional (valor 0) para marcar o final da string.

Literais de string não podem conter novas linhas incorporadas; esta proibição simplifica um pouco a análise da linguagem. Para incluir uma nova linha em uma string, o escape de barra invertida \n pode ser usado, como abaixo.

Existem várias funções de biblioteca padrão para operar com dados de string (não necessariamente constantes) organizadas como uma matriz de charuso desse formato terminado em nulo; veja abaixo .

A sintaxe string literal do C tem sido muito influente e fez seu caminho em muitas outras linguagens, como C ++, Objective-C, Perl, Python, PHP, Java, Javascript, C #, Ruby. Hoje em dia, quase todas as novas linguagens adotam ou se baseiam na sintaxe de strings no estilo C. Linguagens que não possuem essa sintaxe tendem a preceder C.

Escapa de barra invertida

Se desejar incluir aspas duplas dentro da string, isso pode ser feito escapando-se dela com uma barra invertida ( \), por exemplo "This string contains \"double quotes\".",. Para inserir uma barra invertida literal, deve-se duplicá-la, por exemplo "A backslash looks like this: \\".

Barras invertidas podem ser usadas para inserir caracteres de controle, etc., em uma string:

Fuga Significado
\\ Barra invertida literal
\" Citação dupla
\' Citação única
\n Nova linha (alimentação de linha)
\r Retorno de carruagem
\b Backspace
\t Aba horizontal
\f Feed de formulário
\a Alerta (sino)
\v Guia vertical
\? Ponto de interrogação (usado para escapar trigraphs )
%% Marca de porcentagem, apenas strings de formato printf (Nota \% não é padrão e nem sempre é reconhecido)
\OOO Caráter com valor octal OOO (onde OOO é 1-3 dígitos octais, '0' - '7')
\xHH Caractere com valor hexadecimal HH (onde HH é 1 ou mais dígitos hexadecimais, '0' - '9', 'A' - 'F', 'a' - 'f')

O uso de outros escapes de barra invertida não é definido pelo padrão C, embora os fornecedores de compiladores geralmente forneçam códigos de escape adicionais como extensões de linguagem. Uma delas é a sequência de escape \epara o caractere de escape com valor hexadecimal ASCII 1B que não foi adicionado ao padrão C devido à falta de representação em outros conjuntos de caracteres (como EBCDIC ). Ele está disponível em GCC , clang e tcc .

Concatenação literal de string

C tem concatenação de literal de string , o que significa que literais de string adjacentes são concatenados em tempo de compilação; isso permite que strings longas sejam divididas em várias linhas e também permite que literais de string resultantes de definições de pré-processador C e macros sejam anexadas a strings em tempo de compilação:

    printf(__FILE__ ": %d: Hello "
           "world\n", __LINE__);

irá expandir para

    printf("helloworld.c" ": %d: Hello "
           "world\n", 10);

que é sintaticamente equivalente a

    printf("helloworld.c: %d: Hello world\n", 10);

Constantes de personagem

Constantes de caracteres individuais são entre aspas simples, por exemplo 'A', e têm tipo int(em C ++, char). A diferença é que "A"representa uma matriz terminada em nulo de dois caracteres, 'A' e '\ 0', enquanto 'A'representa diretamente o valor do caractere (65 se ASCII for usado). Os mesmos escapes de barra invertida são suportados como para strings, exceto que (é claro) "pode validamente ser usado como um caractere sem ser escapado, enquanto 'que agora deve ser escapado.

Uma constante de caractere não pode estar vazia (ou seja, ''é uma sintaxe inválida), embora uma string possa estar (ela ainda tem o caractere de terminação nulo). Constantes de vários caracteres (por exemplo 'xy') são válidas, embora raramente úteis - elas permitem armazenar vários caracteres em um inteiro (por exemplo, 4 caracteres ASCII podem caber em um inteiro de 32 bits, 8 em um de 64 bits). Como a ordem em que os caracteres são compactados em um intnão é especificada (deixada para a implementação para definir), o uso portátil de constantes de vários caracteres é difícil.

No entanto, em situações limitadas a uma plataforma específica e à implementação do compilador, as constantes de vários caracteres encontram seu uso na especificação de assinaturas. Um caso de uso comum é o OSType , em que a combinação de compiladores Classic Mac OS e seu big-endianness inerente significa que os bytes no inteiro aparecem na ordem exata dos caracteres definidos no literal. A definição por "implementações" populares é de fato consistente: em GCC, Clang e Visual C ++ , '1234'resulta em ASCII. 0x31323334

Strings de caracteres largos

Como o tipo chartem 1 byte de largura, um único charvalor normalmente pode representar no máximo 255 códigos de caracteres distintos, o que não é suficiente para todos os caracteres em uso no mundo todo. Para fornecer melhor suporte para caracteres internacionais, o primeiro padrão C (C89) introduziu caracteres largos (codificados no tipo wchar_t) e cadeias de caracteres largos, que são escritos comoL"Hello world!"

Caracteres largos são mais comumente 2 bytes (usando uma codificação de 2 bytes, como UTF-16 ) ou 4 bytes (geralmente UTF-32 ), mas o Padrão C não especifica a largura para wchar_t, deixando a escolha para o implementador. O Microsoft Windows geralmente usa UTF-16, portanto, a string acima teria 26 bytes para um compilador da Microsoft; o mundo Unix prefere UTF-32, portanto, compiladores como o GCC gerariam uma string de 52 bytes. Uma largura de 2 bytes wchar_tsofre a mesma limitação char, visto que certos caracteres (aqueles fora do BMP ) não podem ser representados em um único wchar_t; mas deve ser representado usando pares substitutos .

O padrão C original especificava apenas funções mínimas para operar com cadeias de caracteres largas; em 1995, o padrão foi modificado para incluir um suporte muito mais amplo, comparável ao das charcordas. As funções relevantes são principalmente nomeadas após seus charequivalentes, com a adição de um "w" ou a substituição de "str" ​​por "wcs"; eles são especificados em <wchar.h>, <wctype.h>contendo funções de classificação e mapeamento de caracteres largos.

O método agora geralmente recomendado de suporte a caracteres internacionais é por meio de UTF-8 , que é armazenado em charmatrizes e pode ser escrito diretamente no código-fonte se estiver usando um editor UTF-8, porque UTF-8 é uma extensão ASCII direta .

Strings de largura variável

Uma alternativa comum wchar_té usar uma codificação de largura variável , por meio da qual um caractere lógico pode se estender por várias posições da string. Strings de largura variável podem ser codificados em literais literalmente, sob o risco de confundir o compilador, ou usando escapes de barra invertida numéricos (por exemplo, "\xc3\xa9"para "é" em UTF-8). A codificação UTF-8 foi projetada especificamente (no Plano 9 ) para compatibilidade com as funções de string de biblioteca padrão; recursos de suporte da codificação incluem a falta de nulos incorporados, nenhuma interpretação válida para subseqüências e ressincronização trivial. As codificações sem esses recursos provavelmente serão incompatíveis com as funções da biblioteca padrão; funções de string com reconhecimento de codificação são frequentemente usadas em tais casos.

Funções de biblioteca

Strings , constantes e variáveis, podem ser manipuladas sem usar a biblioteca padrão . No entanto, a biblioteca contém muitas funções úteis para trabalhar com strings terminadas em nulo.

Estruturas e sindicatos

Estruturas

Estruturas e uniões em C são definidas como contêineres de dados que consistem em uma sequência de membros nomeados de vários tipos. Eles são semelhantes aos registros em outras linguagens de programação. Os membros de uma estrutura são armazenados em locais consecutivos na memória, embora o compilador tenha permissão para inserir preenchimento entre ou depois dos membros (mas não antes do primeiro membro) para eficiência ou como preenchimento necessário para o alinhamento adequado pela arquitetura alvo. O tamanho de uma estrutura é igual à soma dos tamanhos de seus membros mais o tamanho do preenchimento.

Sindicatos

As uniões em C estão relacionadas a estruturas e são definidas como objetos que podem conter (em momentos diferentes) objetos de diferentes tipos e tamanhos. Eles são análogos aos registros variantes em outras linguagens de programação. Ao contrário das estruturas, todos os componentes de uma união referem-se ao mesmo local na memória. Dessa forma, uma união pode ser usada em vários momentos para conter diferentes tipos de objetos, sem a necessidade de criar um objeto separado para cada novo tipo. O tamanho de uma união é igual ao tamanho de seu maior tipo de componente.

Declaração

As estruturas são declaradas com a structpalavra - chave e as uniões são declaradas com a unionpalavra - chave. A palavra-chave do especificador é seguida por um nome de identificador opcional, que é usado para identificar a forma da estrutura ou união. O identificador é seguido pela declaração da estrutura ou corpo da união: uma lista de declarações de membros, contida entre chaves, com cada declaração terminada por um ponto e vírgula. Finalmente, a declaração termina com uma lista opcional de nomes de identificadores, que são declarados como instâncias da estrutura ou união.

Por exemplo, a instrução a seguir declara uma estrutura nomeada sque contém três membros; ele também declarará uma instância da estrutura conhecida como tee:

struct s {
    int   x;
    float y;
    char  *z;
} tee;

E a seguinte declaração irá declarar uma união semelhante nomeada ue uma instância dela nomeada n:

union u {
    int   x;
    float y;
    char  *z;
} n;

Membros de estruturas e sindicatos não podem ter tipo incompleto ou função. Portanto, os membros não podem ser uma instância da estrutura ou união que está sendo declarada (porque está incompleta nesse ponto), mas podem ser indicadores do tipo que está sendo declarado.

Depois que uma estrutura ou corpo de união é declarado e recebe um nome, ele pode ser considerado um novo tipo de dados usando o especificador structou union, conforme apropriado, e o nome. Por exemplo, a seguinte instrução, dada a declaração de estrutura acima, declara uma nova instância da estrutura schamada r:

struct s r;

Também é comum usar o typedefespecificador para eliminar a necessidade da palavra struct- unionchave ou em referências posteriores à estrutura. O primeiro identificador após o corpo da estrutura é considerado o novo nome para o tipo de estrutura (as instâncias da estrutura não podem ser declaradas neste contexto). Por exemplo, a seguinte instrução declarará um novo tipo conhecido como s_type que conterá alguma estrutura:

typedef struct {...} s_type;

As instruções futuras podem então usar o especificador s_type (em vez do structespecificador expandido ...) para se referir à estrutura.

Acessando membros

Os membros são acessados ​​usando o nome da instância de uma estrutura ou união, um ponto ( .) e o nome do membro. Por exemplo, dada a declaração de tee acima, o membro conhecido como y (do tipo float) pode ser acessado usando a seguinte sintaxe:

tee.y

As estruturas são comumente acessadas por meio de ponteiros. Considere o seguinte exemplo que define um ponteiro para tee , conhecido como ptr_to_tee :

struct s *ptr_to_tee = &tee;

O membro y de tee pode então ser acessado desreferenciando ptr_to_tee e usando o resultado como o operando esquerdo:

(*ptr_to_tee).y

Que é idêntico ao mais simples tee.yacima, desde que ptr_to_tee aponte para tee . Devido à precedência do operador ("." Sendo maior que "*"), o mais curto *ptr_to_tee.yé incorreto para este propósito, em vez de ser analisado como *(ptr_to_tee.y)e, portanto, os parênteses são necessários. Como essa operação é comum, C fornece uma sintaxe abreviada para acessar um membro diretamente de um ponteiro. Com essa sintaxe, o nome da instância é substituído pelo nome do ponteiro e o ponto é substituído pela seqüência de caracteres ->. Assim, o seguinte método de acessar y é idêntico aos dois anteriores:

ptr_to_tee->y

Membros de sindicatos são acessados ​​da mesma forma.

Isso pode ser encadeado; por exemplo, em uma lista encadeada, pode-se referir-se ao n->next->nextsegundo nó seguinte (assumindo que n->nextnão seja nulo).

Atribuição

A atribuição de valores a membros individuais de estruturas e uniões é sintaticamente idêntica à atribuição de valores a qualquer outro objeto. A única diferença é que o lvalue da atribuição é o nome do membro, conforme acessado pela sintaxe mencionada acima.

Uma estrutura também pode ser atribuída como uma unidade a outra estrutura do mesmo tipo. Estruturas (e ponteiros para estruturas) também podem ser usados ​​como parâmetros de função e tipos de retorno.

Por exemplo, a instrução a seguir atribui o valor de 74 (o ponto de código ASCII para a letra 't') ao membro denominado x na estrutura tee , de cima:

tee.x = 74;

E a mesma atribuição, usando ptr_to_tee no lugar de tee , ficaria assim:

ptr_to_tee->x = 74;

A atribuição com membros de sindicatos é idêntica.

Outras operações

De acordo com o padrão C, as únicas operações legais que podem ser realizadas em uma estrutura são copiá-la, atribuí-la como uma unidade (ou inicializá-la), obter seu endereço com o &operador unário address-of ( ) e acessar seus membros . Os sindicatos têm as mesmas restrições. Uma das operações proibidas implicitamente é de comparação: as estruturas e os sindicatos não podem ser comparados utilizando instalações de comparação padrão de C ( ==, >, <, etc.).

Campos de bits

C também fornece um tipo especial de membro de estrutura conhecido como campo de bits , que é um número inteiro com um número de bits especificado explicitamente. Um campo de bit é declarado como um membro de estrutura do tipo int, signed int, unsigned int, ou _Bool, após o nome do membro de dois pontos ( :) e o número de bits que ele deve ocupar. O número total de bits em um único campo de bit não deve exceder o número total de bits em seu tipo declarado.

Como uma exceção especial às regras usuais de sintaxe C, é definido pela implementação se um campo de bit declarado como tipo int, sem especificar signedou unsigned, é assinado ou não. Portanto, é recomendável especificar explicitamente signedou unsignedem todos os membros da estrutura para portabilidade.

Os campos sem nome que consistem em apenas dois pontos seguidos por um número de bits também são permitidos; estes indicam preenchimento . Especificar uma largura de zero para um campo sem nome é usado para forçar o alinhamento com uma nova palavra.

Os membros dos campos de bits não têm endereços e, como tal, não podem ser usados ​​com o &operador unário address-of ( ). O sizeofoperador não pode ser aplicado a campos de bits.

A declaração a seguir declara um novo tipo de estrutura conhecido como fe uma instância dele conhecida como g. Os comentários fornecem uma descrição de cada um dos membros:

struct f {
    unsigned int  flag : 1;  /* a bit flag: can either be on (1) or off (0) */
    signed int    num  : 4;  /* a signed 4-bit field; range -7...7 or -8...7 */
    signed int         : 3;  /* 3 bits of padding to round out to 8 bits */
} g;

Inicialização

A inicialização padrão depende do especificador de classe de armazenamento , descrito acima.

Por causa da gramática da linguagem, um inicializador escalar pode ser incluído em qualquer número de pares de chaves. A maioria dos compiladores emite um aviso se houver mais de um par.

int x = 12;
int y = { 23 };     //Legal, no warning
int z = { { 34 } }; //Legal, expect a warning

Estruturas, uniões e arrays podem ser inicializados em suas declarações usando uma lista de inicializadores. A menos que sejam usados ​​designadores, os componentes de um inicializador correspondem aos elementos na ordem em que são definidos e armazenados, portanto, todos os valores anteriores devem ser fornecidos antes do valor de qualquer elemento específico. Qualquer elemento não especificado é definido como zero (exceto para uniões). Mencionar muitos valores de inicialização resulta em erro.

A seguinte declaração irá inicializar uma nova instância da estrutura é conhecida como pi :

struct s {
    int   x;
    float y;
    char  *z;
};

struct s pi = { 3, 3.1415, "Pi" };

Inicializadores designados

Os inicializadores designados permitem que os membros sejam inicializados por nome, em qualquer ordem e sem fornecer explicitamente os valores anteriores. A seguinte inicialização é equivalente à anterior:

struct s pi = { .z = "Pi", .x = 3, .y = 3.1415 };

Usar um designador em um inicializador move o "cursor" de inicialização. No exemplo abaixo, se MAXfor maior que 10, haverá alguns elementos com valor zero no meio de a; se for menor que 10, alguns dos valores fornecidos pelos cinco primeiros inicializadores serão substituídos pelos cinco segundos (se MAXfor menor que 5, haverá um erro de compilação):

int a[MAX] = { 1, 3, 5, 7, 9, [MAX-5] = 8, 6, 4, 2, 0 };

No C89 , um sindicato foi inicializado com um único valor aplicado ao seu primeiro membro. Ou seja, a união u definida acima só poderia ter seu membro int x inicializado:

union u value = { 3 };

Usando um inicializador designado, o membro a ser inicializado não precisa ser o primeiro membro:

union u value = { .y = 3.1415 };

Se uma matriz tem tamanho desconhecido (ou seja, a matriz era de um tipo incompleto ), o número de inicializadores determina o tamanho da matriz e seu tipo se torna completo:

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

Designadores compostos podem ser usados ​​para fornecer inicialização explícita quando listas de inicializadores não adornados podem ser mal interpretadas. No exemplo abaixo, wé declarado como uma matriz de estruturas, cada estrutura consistindo em um membro a(uma matriz de 3 int) e um membro b(an int). O inicializador define o tamanho de wcomo 2 e define os valores do primeiro elemento de cada a:

struct { int a[3], b; } w[] = { [0].a = {1}, [1].a[0] = 2 };

Isso é equivalente a:

struct { int a[3], b; } w[] =
{
   { { 1, 0, 0 }, 0 },
   { { 2, 0, 0 }, 0 } 
};

Não há como especificar a repetição de um inicializador no padrão C.

Literais compostos

É possível pegar emprestada a metodologia de inicialização para gerar uma estrutura composta e literais de matriz:

// pointer created from array literal.
int *ptr = (int[]){ 10, 20, 30, 40 };

// pointer to array.
float (*foo)[3] = &(float[]){ 0.5f, 1.f, -0.5f };

struct s pi = (struct s){ 3, 3.1415, "Pi" };

Literais compostos são frequentemente combinados com inicializadores designados para tornar a declaração mais legível:

pi = (struct s){ .z = "Pi", .x = 3, .y = 3.1415 };

Operadores

Estruturas de controle

C é uma linguagem de formato livre .

O estilo de suporte varia de programador para programador e pode ser objeto de debate. Veja Estilo de recuo para mais detalhes.

Declarações compostas

Nos itens desta seção, qualquer <instrução> pode ser substituída por uma instrução composta . As declarações compostas têm a forma:

{
    <optional-declaration-list>
    <optional-statement-list>
}

e são usados ​​como o corpo de uma função ou em qualquer lugar onde uma única instrução seja esperada. A lista de declarações declara variáveis ​​a serem usadas naquele escopo , e a lista de declarações são as ações a serem executadas. Os colchetes definem seu próprio escopo e as variáveis ​​definidas dentro desses colchetes serão automaticamente desalocadas no colchete de fechamento. Declarações e instruções podem ser livremente misturadas em uma instrução composta (como em C ++ ).

Declarações de seleção

C tem dois tipos de declarações de seleção : a ifdeclaração e a switchdeclaração .

A ifdeclaração está no formato:

if (<expression>)
    <statement1>
else
    <statement2>

Na ifinstrução, se o <expression>entre parênteses for diferente de zero (verdadeiro), o controle passa para <statement1>. Se a elsecláusula estiver presente e <expression>for zero (falso), o controle passará para <statement2>. A else <statement2>parte é opcional e, se ausente, um falso <expression>resultará simplesmente em ignorar o <statement1>. Um elsesempre corresponde ao anterior mais próximo sem correspondência if; colchetes podem ser usados ​​para substituir isso quando necessário, ou para maior clareza.

A switchinstrução faz com que o controle seja transferido para uma das várias instruções, dependendo do valor de uma expressão , que deve ter tipo integral . A subinstrução controlada por um switch é normalmente composta. Qualquer instrução dentro da subinstrução pode ser rotulada com um ou mais caserótulos, que consistem na palavra-chave caseseguida por uma expressão constante e dois pontos (:). A sintaxe é a seguinte:

switch (<expression>)
{
    case <label1> :
        <statements 1>
    case <label2> :
        <statements 2>
        break;
    default :
        <statements 3>
}

Duas das constantes case associadas ao mesmo switch não podem ter o mesmo valor. Pode haver no máximo um defaultrótulo associado a um switch. Se nenhum dos rótulos de caso for igual à expressão nos parênteses a seguir switch, o controle passa para o defaultrótulo ou, se não houver defaultrótulo, a execução continua logo após a construção inteira.

Os switches podem ser aninhados; um rótulo caseou defaultestá associado ao mais interno switchque o contém. As instruções switch podem "falhar", ou seja, quando uma seção de caso concluiu sua execução, as instruções continuarão a ser executadas para baixo até que uma break;instrução seja encontrada. O fall-through é útil em algumas circunstâncias, mas geralmente não é desejado. No exemplo anterior, se <label2>for atingido, as instruções <statements 2>são executadas e nada mais entre colchetes. No entanto, se <label1>for atingido, ambos <statements 1>e <statements 2>são executados, pois não há breakcomo separar as duas instruções case.

É possível, embora incomum, inserir os switchrótulos nos sub-blocos de outras estruturas de controle. Exemplos disso incluem o dispositivo de Duff e a implementação de co-rotinas de Simon Tatham em Putty .

Declarações de iteração

C tem três formas de declaração de iteração :

do
    <statement>
while ( <expression> ) ;

while ( <expression> )
    <statement>

for ( <expression> ; <expression> ; <expression> )
    <statement>

Nas declarações whilee do, a subinstrução é executada repetidamente, desde que o valor de expressionpermaneça diferente de zero (equivalente a verdadeiro). Com while, o teste, incluindo todos os efeitos colaterais de <expression>, ocorre antes de cada iteração (execução de <statement>); com do, o teste ocorre após cada iteração. Portanto, uma doinstrução sempre executa sua subexecução pelo menos uma vez, ao passo que whilepode nem mesmo executar a subexecução.

A declaração:

for (e1; e2; e3)
    s;

é equivalente a:

e1;
while (e2)
{
    s;
cont:
    e3;
}

exceto para o comportamento de uma continue;instrução (que no forloop salta para em e3vez de e2). Se e2estiver em branco, deverá ser substituído por um 1.

Qualquer uma das três expressões no forloop pode ser omitida. Uma segunda expressão ausente torna o whileteste sempre diferente de zero, criando um loop potencialmente infinito.

Desde C99 , a primeira expressão pode assumir a forma de uma declaração, normalmente incluindo um inicializador, como:

for (int i = 0; i < limit; ++i) {
    // ...
}

O escopo da declaração é limitado à extensão do forloop.

Declarações de salto

As instruções de salto transferem o controle incondicionalmente. Existem quatro tipos de declarações salto em C: goto, continue, break, e return.

A gotodeclaração é assim:

goto <identifier> ;

O identificador deve ser um rótulo (seguido por dois pontos) localizado na função atual. O controle é transferido para a instrução rotulada.

Uma continueinstrução pode aparecer apenas dentro de uma instrução de iteração e faz com que o controle passe para a parte de continuação do loop da instrução de iteração envolvente mais interna. Ou seja, dentro de cada uma das declarações

while (expression)
{
    /* ... */
    cont: ;
}

do
{
    /* ... */
    cont: ;
} while (expression);

for (expr1; expr2; expr3) {
     /* ... */
     cont: ;
}

a continuenão contido em uma instrução de iteração aninhada é o mesmo que goto cont.

A breakinstrução é usada para encerrar um forloop, whileloop, doloop ou switchinstrução. O controle passa para a instrução após a instrução encerrada.

Uma função retorna ao seu chamador pela returninstrução. Quando returné seguido por uma expressão, o valor é retornado ao chamador como o valor da função. Encontrar o final da função é equivalente a um returnsem expressão. Nesse caso, se a função for declarada como retornando um valor e o chamador tentar usar o valor retornado, o resultado será indefinido.

Armazenando o endereço de uma etiqueta

O GCC estende a linguagem C com um &&operador unário que retorna o endereço de um rótulo. Este endereço pode ser armazenado em um void*tipo de variável e pode ser usado posteriormente em uma gotoinstrução. Por exemplo, o seguinte é impresso "hi "em um loop infinito:

    void *ptr = &&J1;

J1: printf("hi ");
    goto *ptr;

Este recurso pode ser usado para implementar uma tabela de salto .

Funções

Sintaxe

A definição da função AC consiste em um tipo de retorno ( voidse nenhum valor for retornado), um nome exclusivo, uma lista de parâmetros entre parênteses e várias instruções:

<return-type> functionName( <parameter-list> )
{
    <statements>
    return <expression of type return-type>;
}

Uma função com voidtipo de não retorno deve incluir pelo menos uma returninstrução. Os parâmetros são dadas pelo <parameter-list>, uma lista separada por vírgulas de declarações de parâmetros, cada item na lista de ser um tipo de dados seguido por um identificador: <data-type> <variable-identifier>, <data-type> <variable-identifier>, ....

Se não houver parâmetros, o <parameter-list>pode ser deixado em branco ou, opcionalmente, ser especificado com uma única palavra void.

É possível definir uma função como tendo um número variável de parâmetros, fornecendo a ...palavra - chave como o último parâmetro em vez de um tipo de dados e identificador de variável. Uma função comumente usada para fazer isso é a função de biblioteca padrão printf, que tem a declaração:

int printf (const char*, ...);

A manipulação desses parâmetros pode ser feita usando as rotinas no cabeçalho da biblioteca padrão <stdarg.h>.

Ponteiros de função

Um ponteiro para uma função pode ser declarado da seguinte maneira:

<return-type> (*<function-name>)(<parameter-list>);

O programa a seguir mostra o uso de um ponteiro de função para selecionar entre adição e subtração:

#include <stdio.h>

int (*operation)(int x, int y);

int add(int x, int y)
{
    return x + y;
}

int subtract(int x, int y)
{
    return x - y;
}

int main(int argc, char* args[])
{
   int  foo = 1, bar = 1;

   operation = add;
   printf("%d + %d = %d\n", foo, bar, operation(foo, bar));
   operation = subtract;
   printf("%d - %d = %d\n", foo, bar, operation(foo, bar));
   return 0;
}

Estrutura global

Após o pré-processamento, no nível mais alto, um programa C consiste em uma sequência de declarações no escopo do arquivo. Eles podem ser particionados em vários arquivos de origem separados, que podem ser compilados separadamente; os módulos de objeto resultantes são então vinculados aos módulos de suporte de tempo de execução fornecidos pela implementação para produzir uma imagem executável.

As declarações introduzem funções , variáveis e tipos . As funções C são semelhantes às sub-rotinas do Fortran ou aos procedimentos de Pascal .

Uma definição é um tipo especial de declaração. Uma definição de variável separa o armazenamento e possivelmente o inicializa, uma definição de função fornece seu corpo.

Uma implementação de C fornecendo todas as funções de biblioteca padrão é chamada de implementação hospedada . Os programas escritos para implementações hospedadas são necessários para definir uma função especial chamada main, que é a primeira função chamada quando um programa começa a ser executado.

As implementações hospedadas iniciam a execução do programa invocando a mainfunção, que deve ser definida seguindo um destes protótipos:

int main() {...}
int main(void) {...}
int main(int argc, char *argv[]) {...}
int main(int argc, char **argv) {...}

As duas primeiras definições são equivalentes (e ambas são compatíveis com C ++). Provavelmente depende da preferência individual qual é usado (o padrão C atual contém dois exemplos de main()e dois de main(void), mas o padrão de rascunho C ++ usa main()). O valor de retorno de main(que deve ser int) serve como status de encerramento retornado ao ambiente do host.

O padrão C define valores de retorno 0e EXIT_SUCCESScomo indicadores de sucesso e EXIT_FAILUREcomo indicadores de falha. ( EXIT_SUCCESSe EXIT_FAILUREsão definidos em <stdlib.h>). Outros valores de retorno têm significados definidos pela implementação; por exemplo, no Linux, um programa eliminado por um sinal produz um código de retorno do valor numérico do sinal mais 128.

Um programa C mínimo correto consiste em uma mainrotina vazia , sem argumentos e sem fazer nada:

int main(void){}

Como nenhuma returninstrução está presente, mainretorna 0 na saída. (Este é um recurso de caso especial introduzido no C99 que se aplica apenas a main.)

A mainfunção geralmente chamará outras funções para ajudá-la a realizar seu trabalho.

Algumas implementações não são hospedadas, geralmente porque não se destinam ao uso com um sistema operacional . Essas implementações são chamadas de autônomas no padrão C. Uma implementação independente é livre para especificar como ela lida com a inicialização do programa; em particular, não precisa de um programa para definir uma mainfunção.

As funções podem ser escritas pelo programador ou fornecidas por bibliotecas existentes. As interfaces para o último são geralmente declaradas incluindo arquivos de cabeçalho - com a #include diretiva de pré - processamento - e os objetos da biblioteca são vinculados à imagem executável final. Certas funções de biblioteca, como printf, são definidas pelo padrão C; elas são chamadas de funções de biblioteca padrão .

Uma função pode retornar um valor para o chamador (geralmente outra função C ou o ambiente de hospedagem para a função main). A printffunção mencionada acima retorna quantos caracteres foram impressos, mas esse valor é freqüentemente ignorado.

Passagem de argumento

Em C, os argumentos são passados ​​para funções por valor, enquanto outras linguagens podem passar variáveis por referência . Isso significa que a função receptora obtém cópias dos valores e não tem uma maneira direta de alterar as variáveis ​​originais. Para que uma função altere uma variável passada de outra função, o chamador deve passar seu endereço (um ponteiro para ela), que pode então ser desreferenciado na função receptora. Consulte Ponteiros para obter mais informações.

void incInt(int *y)
{
    (*y)++;  // Increase the value of 'x', in 'main' below, by one
}

int main(void)
{
    int x = 0;
    incInt(&x);  // pass a reference to the var 'x'
    return 0;
}

A função scanf funciona da mesma maneira:

int x;
scanf("%d", &x);

Para passar um ponteiro editável para uma função (como para o propósito de retornar um array alocado ao código de chamada), você deve passar um ponteiro para esse ponteiro: seu endereço.

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

void allocate_array(int ** const a_p, const int A) {
/* 
 allocate array of A ints
 assigning to *a_p alters the 'a' in main()
*/
    *a_p = malloc(sizeof(int) * A); 
}

int main(void) {
    int * a; /* create a pointer to one or more ints, this will be the array */

 /* pass the address of 'a' */
    allocate_array(&a, 42);

/* 'a' is now an array of length 42 and can be manipulated and freed here */

    free(a);
    return 0;
}

O parâmetro int **a_pé um ponteiro para um ponteiro para um int, que é o endereço do ponteiro pdefinido na função principal neste caso.

Parâmetros de matriz

Os parâmetros de função do tipo array podem, à primeira vista, parecer uma exceção à regra de passagem por valor de C. O programa a seguir imprimirá 2, não 1:

#include <stdio.h>

void setArray(int array[], int index, int value)
{
    array[index] = value;
}

int main(void)
{
    int a[1] = {1};
    setArray(a, 0, 2);
    printf ("a[0]=%d\n", a[0]);
    return 0;
}

No entanto, há um motivo diferente para esse comportamento. Na verdade, um parâmetro de função declarado com um tipo de array é tratado como se fosse declarado um ponteiro. Ou seja, a declaração anterior de setArrayé equivalente ao seguinte:

void setArray(int *array, int index, int value)

Ao mesmo tempo, as regras C para o uso de arrays em expressões fazem com que o valor de ana chamada setArrayseja convertido em um ponteiro para o primeiro elemento de array a. Assim, na verdade, este ainda é um exemplo de passagem por valor, com a ressalva de que é o endereço do primeiro elemento da matriz sendo passado por valor, não o conteúdo da matriz.

Diversos

Palavras-chave reservadas

As seguintes palavras são reservadas e não podem ser usadas como identificadores:

As implementações podem reservar outras palavras-chave, como asm, embora as implementações normalmente forneçam palavras-chave não padrão que começam com um ou dois sublinhados.

Sensibilidade a maiúsculas e minúsculas

Identificadores C são sensíveis (por exemplo, foo, FOO, e Foosão os nomes dos diferentes objectos). Alguns linkers podem mapear identificadores externos para um único caso, embora isso seja incomum na maioria dos linkers modernos.

Comentários

O texto que começa com o token /* é tratado como um comentário e ignorado. O comentário termina no próximo */; pode ocorrer dentro de expressões e pode se estender por várias linhas. A omissão acidental do terminador de comentário é problemática porque o terminador de comentário adequadamente construído do próximo comentário será usado para encerrar o comentário inicial e todo o código entre os comentários será considerado um comentário. Os comentários de estilo C não se aninham; ou seja, colocar acidentalmente um comentário dentro de um comentário produz resultados indesejados:

/*
This line will be ignored.
/*
A compiler warning may be produced here. These lines will also be ignored.
The comment opening token above did not start a new comment,
and the comment closing token below will close the comment begun on line 1.
*/
This line and the line below it will not be ignored. Both will likely produce compile errors.
*/

Os comentários de linha no estilo C ++ começam //e se estendem até o final da linha. Este estilo de comentário originou-se em BCPL e tornou-se a sintaxe C válida em C99 ; não está disponível no K&R C original nem em ANSI C :

// this line will be ignored by the compiler

/* these lines
   will be ignored
   by the compiler */

x = *p/*q;  /* this comment starts after the 'p' */

Argumentos de linha de comando

Os parâmetros fornecidos em uma linha de comando são passados ​​para um programa C com duas variáveis ​​predefinidas - a contagem dos argumentos da linha de comando argce os argumentos individuais como cadeias de caracteres na matriz de ponteiro argv. Portanto, o comando:

myFilt p1 p2 p3

resulta em algo como:

m y F eu eu t \ 0 p 1 \ 0 p 2 \ 0 p 3 \ 0
argv [0] argv [1] argv [2] argv [3]

Embora as strings individuais sejam matrizes de caracteres contíguos, não há garantia de que as strings sejam armazenadas como um grupo contíguo.

O nome do programa,, argv[0]pode ser útil ao imprimir mensagens de diagnóstico ou para fazer um binário servir a vários propósitos. Os valores individuais dos parâmetros pode ser acedida com argv[1], argv[2]e argv[3], como mostrado no seguinte programa:

#include <stdio.h>

int main(int argc, char *argv[])
{
    printf("argc\t= %d\n", argc);
    for (int i = 0; i < argc; i++)
        printf("argv[%i]\t= %s\n", i, argv[i]);
}

Ordem de avaliação

Em qualquer expressão razoavelmente complexo, surge a possibilidade de escolher a ordem na qual a avaliar as partes da expressão: podem ser avaliadas na ordem de , , , , ou na ordem de , , , . Formalmente, um compilador C em conformidade pode avaliar as expressões em qualquer ordem entre os pontos de sequência (isso permite que o compilador faça alguma otimização). Os pontos de sequência são definidos por: (1+1)+(3+3)(1+1)+(3+3)(2)+(3+3)(2)+(6)(8)(1+1)+(3+3)(1+1)+(6)(2)+(6)(8)

  • A instrução termina em ponto e vírgula.
  • O operador de sequenciamento : uma vírgula. No entanto, vírgulas que delimitam argumentos de função não são pontos de sequência.
  • Os operadores de curto-circuito : lógico e ( &&, que pode ser lido e então ) e lógico ou ( ||, que pode ser lido ou então ).
  • O operador ternário ( ?:): este operador avalia sua primeira subexpressão primeiro e, em seguida, sua segunda ou terceira (nunca as duas) com base no valor da primeira.
  • Entrada e saída de uma chamada de função (mas não entre avaliações dos argumentos).

As expressões antes de um ponto de sequência são sempre avaliadas antes daquelas após um ponto de sequência. No caso de avaliação de curto-circuito, a segunda expressão pode não ser avaliada dependendo do resultado da primeira expressão. Por exemplo, na expressão , se o primeiro argumento for avaliado como diferente de zero (verdadeiro), o resultado de toda a expressão não pode ser diferente de verdadeiro, portanto, não é avaliado. Da mesma forma, na expressão , se o primeiro argumento for avaliado como zero (falso), o resultado de toda a expressão não pode ser diferente de falso, portanto, não é avaliado. (a() || b())b()(a() && b())b()

Os argumentos para uma chamada de função podem ser avaliados em qualquer ordem, desde que todos sejam avaliados no momento em que a função é inserida. A seguinte expressão, por exemplo, tem comportamento indefinido:

 printf("%s %s\n", argv[i = 0], argv[++i]);

Comportamento indefinido

Um aspecto do padrão C (não exclusivo do C) é que o comportamento de determinado código é considerado "indefinido". Na prática, isso significa que o programa produzido a partir desse código pode fazer qualquer coisa, desde funcionar como o programador pretendia até travar toda vez que for executado.

Por exemplo, o código a seguir produz um comportamento indefinido, porque a variável b é modificada mais de uma vez sem nenhum ponto de sequência intermediário:

#include <stdio.h>

int main(void)
{
    int b = 1;
    int a = b++ + b++;
    printf("%d\n", a);
}

Como não há um ponto de sequência entre as modificações de b em " b ++ + b ++", é possível executar as etapas de avaliação em mais de uma ordem, resultando em uma declaração ambígua. Isso pode ser corrigido reescrevendo o código para inserir um ponto de sequência a fim de impor um comportamento inequívoco, por exemplo:

a = b++;
a += b++;

Veja também

Referências

Em geral

links externos