C (linguagem de programação)

format_list_bulleted Contenido keyboard_arrow_down
ImprimirCitar
Linguagem de programação para uso geral

C (pronunciado – como a letra c) é uma linguagem de programação de computador de uso geral. Foi criado na década de 1970 por Dennis Ritchie e continua sendo amplamente utilizado e influente. Por design, os recursos do C refletem claramente os recursos das CPUs de destino. Ele encontrou uso duradouro em sistemas operacionais, drivers de dispositivo, pilhas de protocolos, embora cada vez menos para software de aplicativo. C é comumente usado em arquiteturas de computadores que vão desde os maiores supercomputadores até os menores microcontroladores e sistemas embarcados.

Um sucessor da linguagem de programação B, C foi originalmente desenvolvido no Bell Labs por Ritchie entre 1972 e 1973 para construir utilitários rodando em Unix. Foi aplicado para reimplementar o kernel do sistema operacional Unix. Durante a década de 1980, C gradualmente ganhou popularidade. Tornou-se uma das linguagens de programação mais amplamente utilizadas, com compiladores C disponíveis para praticamente todas as arquiteturas de computadores e sistemas operacionais modernos. C foi padronizado pela ANSI desde 1989 (ANSI C) e pela Organização Internacional de Padronização (ISO).

C é uma linguagem procedural imperativa, suportando programação estruturada, escopo de variável léxica e recursão, com um sistema de tipo estático. Ele foi projetado para ser compilado para fornecer acesso de baixo nível à memória e construções de linguagem que mapeiam com eficiência as instruções da máquina, tudo com suporte mínimo de tempo de execução. Apesar de seus recursos de baixo nível, a linguagem foi projetada para incentivar a programação entre plataformas. Um programa C compatível com os padrões escrito com a portabilidade em mente pode ser compilado para uma ampla variedade de plataformas de computador e sistemas operacionais com poucas alterações em seu código-fonte.

Desde 2000, C tem consistentemente classificado entre as duas principais linguagens no índice TIOBE, uma medida da popularidade das linguagens de programação.

Visão geral

Dennis Ritchie (direita), o inventor da linguagem de programação C, com Ken Thompson

C é uma linguagem imperativa e procedural na tradição ALGOL. Tem um sistema de tipo estático. Em C, todo código executável está contido em sub-rotinas (também chamadas de "funções", embora não no sentido de programação funcional). Os parâmetros da função são passados por valor, embora os arrays sejam passados como ponteiros, ou seja, o endereço do primeiro item no array. Passar por referência é simulado em C passando explicitamente ponteiros para a coisa sendo referenciada.

O texto fonte do programa C é um código de formato livre. O ponto-e-vírgula separa as instruções e as chaves são usadas para agrupar blocos de instruções.

A linguagem C também apresenta as seguintes características:

  • A linguagem tem um pequeno número fixo de palavras-chave, incluindo um conjunto completo de primitivas de fluxo de controle: if/else, for, do/while, whilee switch. Nomes definidos pelo usuário não se distinguem de palavras-chave por qualquer tipo de sigil.
  • Tem um grande número de operadores aritméticos, bits e lógicos: +,+=,++,&,||, etc.
  • Mais de uma tarefa pode ser realizada em uma única declaração.
  • Funções:
    • Os valores de retorno da função podem ser ignorados, quando não necessário.
    • Função e os ponteiros de dados permitem ad hoc polimorfismo em tempo de execução.
    • As funções não podem ser definidas no âmbito lexical de outras funções.
    • As variáveis podem ser definidas dentro de uma função, com escopo.
    • Uma função pode se chamar, então a recursão é suportada.
  • A digitação de dados é estática, mas fracamente aplicada; todos os dados têm um tipo, mas as conversões implícitas são possíveis.
  • Os tipos definidos pelo usuário (typedef) e compostos são possíveis.
    • Tipos de dados agregados heterogêneos (struct) permitir que os elementos de dados relacionados sejam acessados e atribuídos como uma unidade.
    • A União é uma estrutura com membros sobrepostos; apenas o último membro armazenado é válido.
    • A indexação de raios é uma notação secundária, definida em termos de aritmética de ponteiro. Ao contrário dos structs, os arrays não são objetos de primeira classe: eles não podem ser atribuídos ou comparados usando operadores únicos embutidos. Não há nenhuma palavra-chave "array" em uso ou definição; em vez disso, os suportes quadrados indicam arrays sinteticamente, por exemplo month[11].
    • Os tipos enumerated são possíveis com o enum palavra-chave. Eles são livremente interconversíveis com inteiros.
    • As cordas não são um tipo de dados distinto, mas são implementadas convencionalmente como arrays de caracteres nulos-terminados.
  • O acesso de baixo nível à memória do computador é possível através da conversão de endereços de máquina para ponteiros.
  • Procedimentos (subrotinas não retornando valores) são um caso especial de função, com um tipo de retorno não digitado void.
  • A memória pode ser alocada a um programa com chamadas para rotinas de biblioteca.
  • Um pré-processador executa a definição de macro, inclusão de arquivos de código fonte e compilação condicional.
  • Há uma forma básica de modularidade: os arquivos podem ser compilados separadamente e ligados em conjunto, com controle sobre quais funções e objetos de dados são visíveis para outros arquivos via estática e extern atributos.
  • Funcionalidade complexa, como I/O, manipulação de strings e funções matemáticas são sempre delegadas a rotinas de biblioteca.
  • O código gerado após a compilação tem necessidades relativamente simples na plataforma subjacente, o que o torna adequado para a criação de sistemas operacionais e para uso em sistemas embarcados.

Embora C não inclua certos recursos encontrados em outras linguagens (como orientação a objetos e coleta de lixo), eles podem ser implementados ou emulados, geralmente por meio do uso de bibliotecas externas (por exemplo, o GLib Object System ou o coletor de lixo Boehm).

Relações com outros idiomas

Muitas linguagens posteriores foram emprestadas direta ou indiretamente do C, incluindo C++, C#, shell C do Unix, D, Go, Java, JavaScript (incluindo transpilers), Julia, Limbo, LPC, Objective-C, Perl, PHP, Python, Ruby, Rust, Swift, Verilog e SystemVerilog (linguagens de descrição de hardware). Essas linguagens extraíram muitas de suas estruturas de controle e outros recursos básicos de C. A maioria delas (Python sendo uma exceção dramática) também expressam uma sintaxe altamente semelhante a C e tendem a combinar a expressão reconhecível e a sintaxe de instrução de C com o tipo subjacente sistemas, modelos de dados e semântica que podem ser radicalmente diferentes.

História

Desenvolvimentos iniciais

Linha do tempo de linguagem C
Ano Nome informativo Padrão C
1972 Nascimento
1978 K&R C
1989-1990 ANSI C, ISO C ISO/IEC 9899:1990
1999 C99 ISO/IEC 9899:1999
2011 C11, C1x ISO/IEC 9899:2011
2018 C17 ISO/IEC 9899:2018
2023. C23, C2x TBA

A origem do C está intimamente ligada ao desenvolvimento do sistema operacional Unix, originalmente implementado em linguagem assembly em um PDP-7 por Dennis Ritchie e Ken Thompson, incorporando várias ideias de colegas. Eventualmente, eles decidiram portar o sistema operacional para um PDP-11. A versão PDP-11 original do Unix também foi desenvolvida em linguagem assembly.

B

Thompson queria uma linguagem de programação para desenvolver utilitários para a nova plataforma. A princípio, ele tentou escrever um compilador Fortran, mas logo desistiu da ideia. Em vez disso, ele criou uma versão reduzida da linguagem de programação de sistemas BCPL recentemente desenvolvida. A descrição oficial do BCPL não estava disponível na época e Thompson modificou a sintaxe para ser menos prolixa e semelhante a um ALGOL simplificado conhecido como SMALGOL. Thompson chamou o resultado de B. Ele descreveu B como "semântica BCPL com muita sintaxe SMALGOL". Como BCPL, B tinha um compilador bootstrap para facilitar a portabilidade para novas máquinas. No entanto, poucos utilitários foram escritos em B porque era muito lento e não podia aproveitar os recursos do PDP-11, como endereçamento de byte.

Nova versão B e primeira versão C

Em 1971, Ritchie começou a melhorar o B, para utilizar os recursos do mais poderoso PDP-11. Uma adição significativa foi um tipo de dados de caractere. Ele chamou isso de Novo B (NB). Thompson começou a usar o NB para escrever o kernel do Unix, e seus requisitos moldaram a direção do desenvolvimento da linguagem. Até 1972, tipos mais ricos foram adicionados à linguagem NB: NB tinha arrays de int e char. Ponteiros, a capacidade de gerar ponteiros para outros tipos, matrizes de todos os tipos e tipos a serem retornados de funções também foram adicionados. Arrays dentro de expressões tornaram-se ponteiros. Um novo compilador foi escrito e a linguagem foi renomeada para C.

O compilador C e alguns utilitários feitos com ele foram incluídos na versão 2 do Unix, também conhecida como Research Unix.

Estruturas e a reescrita do kernel Unix

Na versão 4 do Unix, lançada em novembro de 1973, o kernel do Unix foi extensivamente reimplementado em C. Nessa época, a linguagem C havia adquirido alguns recursos poderosos, como os tipos struct.

O pré-processador foi introduzido por volta de 1973 a pedido de Alan Snyder e também em reconhecimento à utilidade dos mecanismos de inclusão de arquivos disponíveis em BCPL e PL/I. Sua versão original fornecia apenas arquivos incluídos e substituições simples de strings: #include e #define de macros sem parâmetros. Logo depois, foi estendido, principalmente por Mike Lesk e depois por John Reiser, para incorporar macros com argumentos e compilação condicional.

Unix foi um dos primeiros kernels de sistema operacional implementado em uma linguagem diferente de assembly. Instâncias anteriores incluem o sistema Multics (que foi escrito em PL/I) e o Master Control Program (MCP) para o Burroughs B5000 (que foi escrito em ALGOL) em 1961. Por volta de 1977, Ritchie e Stephen C. Johnson fizeram mais alterações no a linguagem para facilitar a portabilidade do sistema operacional Unix. Johnson's Portable C Compiler serviu de base para várias implementações de C em novas plataformas.

K&R C

A capa do livro A linguagem de programação C, primeira edição, por Brian Kernighan e Dennis Ritchie

Em 1978, Brian Kernighan e Dennis Ritchie publicaram a primeira edição de A linguagem de programação C. Este livro, conhecido pelos programadores C como K&R, serviu por muitos anos como uma especificação informal da linguagem. A versão de C que ele descreve é comumente referida como "K&R C". Como foi lançado em 1978, também é conhecido como C78. A segunda edição do livro cobre o padrão ANSI C posterior, descrito abaixo.

K&R introduziu vários recursos de linguagem:

  • Biblioteca I/O padrão
  • long int tipo de dados
  • unsigned int tipo de dados
  • Operadores de atribuição compostos do formulário =op (como =-) foram alterados para o formulário op= (isto é, -=) remover a ambiguidade semântica criada por construções como i=-10, que tinha sido interpretado como i =- 10 (decrente) i por 10) em vez do possivelmente pretendido i = -10 (em inglês) i be −10).

Mesmo após a publicação do padrão ANSI de 1989, por muitos anos K&RC ainda era considerado o "menor denominador comum" ao qual os programadores C se restringiam quando a portabilidade máxima era desejada, uma vez que muitos compiladores mais antigos ainda estavam em uso e porque o código K&RC cuidadosamente escrito também pode ser o Padrão C legal.

Nas primeiras versões de C, apenas as funções que retornam tipos diferentes de int devem ser declaradas se usadas antes da definição da função; presumia-se que as funções usadas sem declaração prévia retornassem o tipo int.

Por exemplo:

longo alguns_funções(); Não. Esta é uma declaração de função, para que o compilador possa conhecer o nome e o tipo de retorno desta função. *- Não. outros_funções(); /* Outra declaração de função. Porque esta é uma versão inicial de C, há um tipo "int" implícito aqui. Um comentário mostra onde o especificador tipo "int" explícito seria necessário em versões posteriores. *- Não. call_function() Não. Esta é uma definição de função, incluindo o corpo do código a seguir no { curly colchetes }. Como nenhum tipo de retorno é especificado, a função retorna implicitamente um 'int' nesta versão inicial de C. */( longo test1; registo - Não. test2; /* Novamente, note que 'int' não é necessário aqui. O especifier tipo 'int' */ /* no comentário seria necessário em versões posteriores de C. */ Não. A palavra-chave "registrar" indica ao compilador que esta variável deve */ /* idealmente ser armazenado em um registro em oposição a dentro do frame da pilha. * test1 = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = alguns_funções(); se (test1 > 1) test2 = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 0; mais test2 = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = outros_funções(); retorno test2;?

Os especificadores de tipo int que são comentados podem ser omitidos em K&R C, mas são necessários em padrões posteriores.

Como as declarações de função K&R não incluíam nenhuma informação sobre argumentos de função, as verificações de tipo de parâmetro de função não eram executadas, embora alguns compiladores emitem uma mensagem de aviso se uma função local fosse chamada com o número errado de argumentos ou se vários chamadas para uma função externa usavam diferentes números ou tipos de argumentos. Ferramentas separadas, como o utilitário lint do Unix, foram desenvolvidas para (entre outras coisas) verificar a consistência do uso de funções em vários arquivos de origem.

Nos anos seguintes à publicação do K&R C, vários recursos foram adicionados à linguagem, suportados por compiladores da AT&T (em particular PCC) e alguns outros fornecedores. Estes incluíram:

  • void funções (ou seja, funções sem valor de retorno)
  • funções retornando struct ou union tipos (anteriormente apenas um único ponteiro, inteiro ou flutuador poderia ser retornado)
  • atribuição para struct tipos de dados
  • tipos enumerados (anteriormente, foram utilizadas definições pré-processadoras para valores fixos inteiros, por exemplo. #define GREEN 3)

O grande número de extensões e a falta de acordo sobre uma biblioteca padrão, juntamente com a popularidade da linguagem e o fato de que nem mesmo os compiladores Unix implementaram com precisão a especificação K&R, levaram à necessidade de padronização.

ANSI C e ISO C

Durante o final dos anos 1970 e 1980, as versões do C foram implementadas para uma ampla variedade de computadores mainframe, minicomputadores e microcomputadores, incluindo o IBM PC, pois sua popularidade começou a aumentar significativamente.

Em 1983, o American National Standards Institute (ANSI) formou um comitê, X3J11, para estabelecer uma especificação padrão de C. X3J11 baseou o padrão C na implementação do Unix; no entanto, a parte não portátil da biblioteca Unix C foi entregue ao grupo de trabalho IEEE 1003 para se tornar a base para o padrão POSIX de 1988. Em 1989, o padrão C foi ratificado como ANSI X3.159-1989 "Programming Language C". Essa versão da linguagem costuma ser chamada de ANSI C, Standard C ou, às vezes, C89.

Em 1990, o padrão ANSI C (com alterações de formatação) foi adotado pela Organização Internacional de Padronização (ISO) como ISO/IEC 9899:1990, às vezes chamado de C90. Portanto, os termos "C89" e "C90" referem-se à mesma linguagem de programação.

A ANSI, como outros órgãos nacionais de padronização, não desenvolve mais o padrão C de forma independente, mas segue o padrão C internacional, mantido pelo grupo de trabalho ISO/IEC JTC1/SC22/WG14. A adoção nacional de uma atualização do padrão internacional normalmente ocorre dentro de um ano após a publicação da ISO.

Um dos objetivos do processo de padronização C era produzir um superconjunto de K&R C, incorporando muitos dos recursos não oficiais introduzidos posteriormente. O comitê de padrões também incluiu vários recursos adicionais, como protótipos de função (emprestados de C++), ponteiros void, suporte para conjuntos de caracteres e localidades internacionais e aprimoramentos de pré-processador. Embora a sintaxe para declarações de parâmetros tenha sido aumentada para incluir o estilo usado em C++, a interface K&R continuou a ser permitida, para compatibilidade com o código-fonte existente.

O C89 é suportado pelos compiladores C atuais, e o código C mais moderno é baseado nele. Qualquer programa escrito apenas no padrão C e sem quaisquer suposições dependentes de hardware será executado corretamente em qualquer plataforma com uma implementação C em conformidade, dentro de seus limites de recursos. Sem essas precauções, os programas podem compilar apenas em uma determinada plataforma ou com um compilador específico, devido, por exemplo, ao uso de bibliotecas não padrão, como bibliotecas GUI, ou à dependência de atributos específicos do compilador ou da plataforma, como como o tamanho exato dos tipos de dados e byte endianness.

Nos casos em que o código deve ser compilado por compiladores em conformidade com o padrão ou baseados em K&R C, a macro __STDC__ pode ser usada para dividir o código em seções Standard e K&R para evitar o uso em um compilador baseado em K&R C de recursos disponíveis apenas no padrão C.

Após o processo de padronização ANSI/ISO, a especificação da linguagem C permaneceu relativamente estática por vários anos. Em 1995, a Emenda Normativa 1 ao padrão 1990 C (ISO/IEC 9899/AMD1:1995, conhecido informalmente como C95) foi publicada, para corrigir alguns detalhes e adicionar suporte mais amplo para conjuntos de caracteres internacionais.

C99

O padrão C foi revisado no final dos anos 90, levando à publicação do ISO/IEC 9899:1999 em 1999, que é comumente referido como "C99". Desde então, foi alterado três vezes pela Corrigenda Técnica.

C99 introduziu vários novos recursos, incluindo funções inline, vários novos tipos de dados (incluindo long long int e um tipo complex para representar números complexos), arrays de comprimento variável e membros de matriz flexíveis, suporte aprimorado para ponto flutuante IEEE 754, suporte para macros variáveis (macros de aridade variável) e suporte para comentários de uma linha começando com //, como em BCPL ou C++. Muitos deles já foram implementados como extensões em vários compiladores C.

O C99 é em sua maior parte compatível com versões anteriores do C90, mas é mais rigoroso em alguns aspectos; em particular, uma declaração que carece de um especificador de tipo não tem mais int assumido implicitamente. Uma macro padrão __STDC_VERSION__ é definida com o valor 199901L para indicar que o suporte C99 está disponível. GCC, Solaris Studio e outros compiladores C agora suportam muitos ou todos os novos recursos do C99. O compilador C no Microsoft Visual C++, no entanto, implementa o padrão C89 e as partes do C99 que são necessárias para compatibilidade com C++11.

Além disso, o padrão C99 requer suporte para identificadores Unicode na forma de caracteres de escape (por exemplo, u0040 ou U0001f431) e sugere suporte para nomes Unicode brutos.

C11

Em 2007, o trabalho começou em outra revisão do padrão C, informalmente chamado de "C1X" até a publicação oficial da ISO/IEC 9899:2011 em 2011-12-08. O comitê de padrões C adotou diretrizes para limitar a adoção de novos recursos que não foram testados por implementações existentes.

O padrão C11 adiciona vários novos recursos ao C e à biblioteca, incluindo macros genéricas de tipo, estruturas anônimas, suporte Unicode aprimorado, operações atômicas, multithreading e funções de verificação de limites. Ele também torna opcionais algumas partes da biblioteca C99 existente e melhora a compatibilidade com C++. A macro padrão __STDC_VERSION__ é definida como 201112L para indicar que o suporte C11 está disponível.

C17

Publicado em junho de 2018 como ISO/IEC 9899:2018, C17 é o padrão atual para a linguagem de programação C. Ele não apresenta novos recursos de linguagem, apenas correções técnicas e esclarecimentos para defeitos no C11. A macro padrão __STDC_VERSION__ é definida como 201710L.

C2x

C2x é um nome informal para a próxima (após C17) principal revisão padrão da linguagem C. Espera-se que seja votado em 2023 e, portanto, seria chamado de C23.

C incorporado

Historicamente, a programação C incorporada requer extensões não padronizadas para a linguagem C para oferecer suporte a recursos exóticos, como aritmética de ponto fixo, vários bancos de memória distintos e operações básicas de E/S.

Em 2008, o C Standards Committee publicou um relatório técnico estendendo a linguagem C para abordar essas questões, fornecendo um padrão comum para todas as implementações aderirem. Ele inclui vários recursos não disponíveis em C normal, como aritmética de ponto fixo, espaços de endereço nomeados e endereçamento básico de hardware de E/S.

Sintaxe

C tem uma gramática formal especificada pelo padrão C. Finais de linha geralmente não são significativos em C; no entanto, os limites de linha têm significado durante a fase de pré-processamento. Comentários podem aparecer entre os delimitadores /* e */, ou (desde C99) seguindo // até o final da linha. Os comentários delimitados por /* e */ não são aninhados e essas sequências de caracteres não são interpretadas como delimitadores de comentários se aparecerem dentro de strings ou literais de caracteres.

Os arquivos fonte C contêm declarações e definições de função. As definições de função, por sua vez, contêm declarações e declarações. As declarações definem novos tipos usando palavras-chave como struct, union e enum ou atribuem tipos e talvez reservam armazenamento para novas variáveis, geralmente escrevendo o tipo seguido do nome da variável. Palavras-chave como char e int especificam tipos integrados. As seções do código são colocadas entre chaves ({ e }, às vezes chamadas de "colchetes") para limitar o escopo das declarações e agir como um único declaração para estruturas de controle.

Como uma linguagem imperativa, C usa declarações para especificar ações. A instrução mais comum é uma instrução de expressão, que consiste em uma expressão a ser avaliada, seguida por um ponto e vírgula; como efeito colateral da avaliação, funções podem ser chamadas e variáveis podem receber novos valores. Para modificar a execução sequencial normal de instruções, C fornece várias instruções de fluxo de controle identificadas por palavras-chave reservadas. A programação estruturada é suportada pela execução condicional if... [else] e por do... while, while e for execução iterativa (looping). A instrução for tem expressões separadas de inicialização, teste e reinicialização, algumas ou todas podem ser omitidas. break e continue podem ser usados dentro do loop. Break é usado para deixar a instrução de loop envolvente mais interna e continue é usado para pular para sua reinicialização. Há também uma instrução goto não estruturada que ramifica diretamente para o rótulo designado dentro da função. switch seleciona um case a ser executado com base no valor de uma expressão inteira. Diferente de muitas outras linguagens, o fluxo de controle passará para o próximo case, a menos que seja encerrado por um break.

As expressões podem usar uma variedade de operadores integrados e podem conter chamadas de função. A ordem na qual os argumentos para funções e operandos para a maioria dos operadores são avaliados não é especificada. As avaliações podem até ser intercaladas. No entanto, todos os efeitos colaterais (incluindo armazenamento em variáveis) ocorrerão antes do próximo "ponto de sequência"; os pontos de sequência incluem o final de cada instrução de expressão e a entrada e o retorno de cada chamada de função. Os pontos de sequência também ocorrem durante a avaliação de expressões que contêm determinados operadores (&&, ||, ?: e o operador vírgula). Isso permite um alto grau de otimização do código objeto pelo compilador, mas exige que os programadores C tomem mais cuidado para obter resultados confiáveis do que o necessário para outras linguagens de programação.

Kernighan e Ritchie dizem na Introdução de A Linguagem de Programação C: "C, como qualquer outra linguagem, tem suas falhas. Alguns dos operadores têm a precedência errada; algumas partes da sintaxe poderiam ser melhores." O padrão C não tentou corrigir muitos desses defeitos, devido ao impacto de tais mudanças no software já existente.

Conjunto de caracteres

O conjunto básico de caracteres de origem C inclui os seguintes caracteres:

  • Letras minúsculas e minúsculas do alfabeto latino básico ISO: az AZ
  • dígitos decimais: 09
  • Personagens gráficos: ! " # % & ' () * + -. /:; ? [ ] ^ _ { | } ~
  • Personagens do Whitespace: espaço, guia horizontal, guia vertical, feed de formulário, nova linha

Nova linha indica o fim de uma linha de texto; ele não precisa corresponder a um único caractere real, embora por conveniência C o trate como um.

Caracteres codificados multi-byte adicionais podem ser usados em strings literais, mas eles não são totalmente portáveis. O padrão C mais recente (C11) permite que caracteres Unicode multinacionais sejam incorporados de forma portátil no texto de origem C usando a codificação uXXXX ou UXXXXXXXX (onde o X denota um caractere hexadecimal), embora esse recurso ainda não seja amplamente implementado.

O conjunto básico de caracteres de execução C contém os mesmos caracteres, junto com representações para alerta, backspace e retorno de carro. O suporte em tempo de execução para conjuntos de caracteres estendidos aumentou a cada revisão do padrão C.

Palavras reservadas

O C89 possui 32 palavras reservadas, também conhecidas como palavras-chave, que são as palavras que não podem ser utilizadas para outros fins que não sejam aqueles para os quais foram pré-definidas:

  • auto
  • break
  • case
  • char
  • const
  • continue
  • default
  • do
  • double
  • else
  • enum
  • extern
  • float
  • for
  • goto
  • if
  • int
  • long
  • register
  • return
  • short
  • signed
  • sizeof
  • static
  • struct
  • switch
  • typedef
  • union
  • unsigned
  • void
  • volatile
  • while

C99 reservou mais cinco palavras:

  • _Bool
  • _Complex
  • _Imaginary
  • inline
  • restrict

C11 reservou mais sete palavras:

  • _Alignas
  • _Alignof
  • _Atomic
  • _Generic
  • _Noreturn
  • _Static_assert
  • _Thread_local

A maioria das palavras recentemente reservadas começa com um sublinhado seguido por uma letra maiúscula, porque os identificadores dessa forma foram previamente reservados pelo padrão C para uso apenas por implementações. Como o código-fonte do programa existente não deveria estar usando esses identificadores, ele não seria afetado quando as implementações C começassem a oferecer suporte a essas extensões para a linguagem de programação. Alguns cabeçalhos padrão definem sinônimos mais convenientes para identificadores sublinhados. A linguagem anteriormente incluía uma palavra reservada chamada entry, mas isso raramente era implementado e agora foi removido como uma palavra reservada.

Operadores

C suporta um rico conjunto de operadores, que são símbolos usados dentro de uma expressão para especificar as manipulações a serem executadas durante a avaliação dessa expressão. C tem operadores para:

  • aritmética: +, -, *, /, %
  • Atribuição: =
  • atribuição aumentada: +=,-=,*=,/=,%=,&=,|=,^=,<<=,>>=
  • lógica bitwise: ~, &, |, ^
  • turnos bitwise: <<, >>
  • lógica booleana: !, &&, ||
  • avaliação condicional:
  • Teste de igualdade:
  • funções de chamada: ()
  • incremento e decrement: ++, --
  • seleção de membros: ., ->
  • tamanho do objeto: sizeof
  • relações de ordem: <, <=, >, >=
  • referência e dereferência: &, *, [ ]
  • sequenciamento:
  • agrupamento de subexpressão: ()
  • conversão de tipo: (typename)

C usa o operador = (usado em matemática para expressar igualdade) para indicar atribuição, seguindo o precedente de Fortran e PL/I, mas diferente de ALGOL e seus derivados. C usa o operador == para testar a igualdade. A semelhança entre esses dois operadores (atribuição e igualdade) pode resultar no uso acidental de um no lugar do outro e, em muitos casos, o erro não gera mensagem de erro (embora alguns compiladores produzam avisos). Por exemplo, a expressão condicional if (a == b + 1) pode ser escrita erroneamente como if (a = b + 1), que será avaliada como verdadeira se a não é zero após a atribuição.

A precedência do operador C nem sempre é intuitiva. Por exemplo, o operador == vincula-se mais fortemente do que (é executado antes) dos operadores & (AND bit a bit) e | (bitwise OR) em expressões como x & 1 == 0, que deve ser escrito como (x & 1) == 0 se essa for a intenção do codificador.

"Olá, mundo" exemplo

"Olá, Mundo!" programa de Brian Kernighan (1978)

O "olá, mundo" exemplo, que apareceu na primeira edição do K&R, tornou-se o modelo para um programa introdutório na maioria dos livros didáticos de programação. O programa imprime "olá, mundo" à saída padrão, que geralmente é um terminal ou tela.

A versão original era:

principal()( Impressão("Olá, mundoNão.");?

Um padrão "hello, world" programa é:

#include # - Não. principal(vazio)( Impressão("Olá, mundoNão.");?

A primeira linha do programa contém uma diretiva de pré-processamento, indicada por #include. Isso faz com que o compilador substitua essa linha pelo texto inteiro do cabeçalho padrão stdio.h, que contém declarações para funções de entrada e saída padrão, como printf e escanear. Os colchetes ao redor de stdio.h indicam que stdio.h pode ser localizado usando uma estratégia de pesquisa que prefere cabeçalhos fornecidos com o compilador a outros cabeçalhos com o mesmo nome, ao contrário para aspas duplas que normalmente incluem arquivos de cabeçalho locais ou específicos do projeto.

A próxima linha indica que uma função chamada main está sendo definida. A função main serve a um propósito especial em programas C; o ambiente de tempo de execução chama a função main para iniciar a execução do programa. O especificador de tipo int indica que o valor retornado ao chamador (neste caso, o ambiente de tempo de execução) como resultado da avaliação da função main, é um inteiro. A palavra-chave void como uma lista de parâmetros indica que esta função não aceita argumentos.

A chave de abertura indica o início da definição da função main.

A próxima linha chama (desvia a execução para) uma função chamada printf, que neste caso é fornecida de uma biblioteca do sistema. Nesta chamada, a função printf é passada (fornecida com) um único argumento, o endereço do primeiro caractere na string literal "hello, mundon". A string literal é uma matriz sem nome com elementos do tipo char, configurada automaticamente pelo compilador com um caractere final de valor 0 para marcar o final da matriz (printf precisa saber disso). O n é uma sequência de escape que C traduz para um caractere nova linha, que na saída significa o fim da linha atual. O valor de retorno da função printf é do tipo int, mas é descartado silenciosamente, pois não é utilizado. (Um programa mais cuidadoso pode testar o valor de retorno para determinar se a função printf foi ou não bem-sucedida.) O ponto-e-vírgula ; encerra a instrução.

A chave de fechamento indica o final do código para a função main. De acordo com a especificação C99 e mais recente, a função main, ao contrário de qualquer outra função, retornará implicitamente um valor de 0 ao atingir o } que termina a função. (Anteriormente, era necessária uma instrução return 0; explícita.) Isso é interpretado pelo sistema de tempo de execução como um código de saída indicando uma execução bem-sucedida.

Tipos de dados

1999 ISO C Concepts.png

O sistema de tipos em C é estático e fracamente tipado, o que o torna semelhante ao sistema de tipos de descendentes de ALGOL, como Pascal. Existem tipos integrados para inteiros de vários tamanhos, com e sem sinal, números de ponto flutuante e tipos enumerados (enum). O tipo inteiro char geralmente é usado para caracteres de byte único. C99 adicionou um tipo de dados booleano. Também existem tipos derivados, incluindo matrizes, ponteiros, registros (struct) e uniões (union).

C é frequentemente usado em programação de sistemas de baixo nível, onde escapes do sistema de tipos podem ser necessários. O compilador tenta garantir a correção de tipo da maioria das expressões, mas o programador pode substituir as verificações de várias maneiras, usando um type cast para converter explicitamente um valor de um tipo para outro ou usando ponteiros ou uniões para reinterpretar os bits subjacentes de um objeto de dados de alguma outra maneira.

Alguns acham a sintaxe de declaração de C não intuitiva, particularmente para ponteiros de função. (A ideia de Ritchie era declarar identificadores em contextos semelhantes ao seu uso: "declaração reflete o uso".)

As conversões aritméticas usuais do C' permitem a geração de código eficiente, mas às vezes podem produzir resultados inesperados. Por exemplo, uma comparação de inteiros com e sem sinal de largura igual requer uma conversão do valor com sinal para sem sinal. Isso pode gerar resultados inesperados se o valor com sinal for negativo.

Ponteiros

C suporta o uso de ponteiros, um tipo de referência que registra o endereço ou localização de um objeto ou função na memória. Os ponteiros podem ser desreferenciados para acessar dados armazenados no endereço apontado ou para invocar uma função apontada. Ponteiros podem ser manipulados usando atribuição ou aritmética de ponteiro. A representação em tempo de execução de um valor de ponteiro é normalmente um endereço de memória bruto (talvez aumentado por um campo de deslocamento dentro da palavra), mas como o tipo de um ponteiro inclui o tipo da coisa apontada, expressões incluindo ponteiros podem ser verificado quanto ao tipo em tempo de compilação. A aritmética de ponteiro é dimensionada automaticamente pelo tamanho do tipo de dados apontado.

Ponteiros são usados para muitos propósitos em C. Strings de texto são comumente manipuladas usando ponteiros em arrays de caracteres. A alocação dinâmica de memória é executada usando ponteiros; o resultado de um malloc geralmente é convertido para o tipo de dados a ser armazenado. Muitos tipos de dados, como árvores, são comumente implementados como objetos struct alocados dinamicamente e vinculados usando ponteiros. Ponteiros para outros ponteiros são frequentemente usados em arrays multidimensionais e arrays de objetos struct. Ponteiros para funções (ponteiros de função) são úteis para passar funções como argumentos para funções de ordem superior (como qsort ou bsearch), em tabelas de despacho ou como retornos de chamada para manipuladores de eventos.

Um valor de ponteiro nulo aponta explicitamente para nenhum local válido. Desreferenciar um valor de ponteiro nulo é indefinido, geralmente resultando em uma falha de segmentação. Valores de ponteiro nulo são úteis para indicar casos especiais, como nenhum "próximo" ponteiro no nó final de uma lista vinculada ou como uma indicação de erro de funções que retornam ponteiros. Em contextos apropriados no código-fonte, como para atribuir a uma variável de ponteiro, uma constante de ponteiro nulo pode ser escrita como 0, com ou sem conversão explícita para um tipo de ponteiro, ou como a macro NULL definida por vários cabeçalhos padrão. Em contextos condicionais, os valores de ponteiro nulo são avaliados como falso, enquanto todos os outros valores de ponteiro são avaliados como verdadeiro.

Os ponteiros vazios (void *) apontam para objetos de tipo não especificado e, portanto, podem ser usados como "genéricos" ponteiros de dados. Como o tamanho e o tipo do objeto apontado não são conhecidos, os ponteiros void não podem ser desreferenciados, nem a aritmética de ponteiros é permitida neles, embora possam ser facilmente (e em muitos contextos implicitamente são) convertidos de e para qualquer outro ponteiro de objeto tipo.

O uso descuidado de ponteiros é potencialmente perigoso. Como geralmente não são verificados, uma variável de ponteiro pode ser feita para apontar para qualquer local arbitrário, o que pode causar efeitos indesejáveis. Embora ponteiros usados apropriadamente apontem para locais seguros, eles podem ser feitos para apontar para locais inseguros usando aritmética de ponteiro inválido; os objetos para os quais eles apontam podem continuar a ser usados após a desalocação (ponteiros pendentes); eles podem ser usados sem terem sido inicializados (ponteiros selvagens); ou eles podem ser atribuídos diretamente a um valor inseguro usando uma conversão, união ou por meio de outro ponteiro corrompido. Em geral, C é permissivo ao permitir a manipulação e conversão entre tipos de ponteiro, embora os compiladores normalmente forneçam opções para vários níveis de verificação. Algumas outras linguagens de programação abordam esses problemas usando tipos de referência mais restritivos.

Matrizes

Os tipos de matriz em C são tradicionalmente de tamanho fixo e estático especificado em tempo de compilação. O padrão C99 mais recente também permite uma forma de arrays de comprimento variável. No entanto, também é possível alocar um bloco de memória (de tamanho arbitrário) em tempo de execução, usando a função malloc da biblioteca padrão, e tratá-lo como um array.

Como os arrays são sempre acessados (na verdade) por meio de ponteiros, os acessos aos arrays normalmente não são verificados em relação ao tamanho do array subjacente, embora alguns compiladores possam fornecer verificação de limites como uma opção. Violações de limites de matriz são, portanto, possíveis e podem levar a várias repercussões, incluindo acessos ilegais à memória, corrupção de dados, saturações de buffer e exceções de tempo de execução.

C não possui uma provisão especial para declarar arrays multidimensionais, mas depende da recursão dentro do sistema de tipos para declarar arrays de arrays, o que efetivamente realiza a mesma coisa. Os valores de índice da "matriz multidimensional" pode ser pensado como crescente na ordem da linha principal. Matrizes multidimensionais são comumente usadas em algoritmos numéricos (principalmente da álgebra linear aplicada) para armazenar matrizes. A estrutura da matriz C é adequada para essa tarefa específica. No entanto, nas primeiras versões de C, os limites do array devem ser valores fixos conhecidos ou passados explicitamente para qualquer sub-rotina que os exija, e arrays de arrays de tamanho dinâmico não podem ser acessados usando indexação dupla. (Uma solução alternativa para isso era alocar a matriz com um "vetor linha" de ponteiros para as colunas.) A C99 introduziu "matrizes de comprimento variável" que abordam esta questão.

O exemplo a seguir usando C moderno (C99 ou posterior) mostra a alocação de um array bidimensional no heap e o uso de indexação de array multidimensional para acessos (que pode usar verificação de limites em muitos compiladores C):

- Não. Funk(- Não. N, - Não. M)( flutuar (*p)NNão.M] = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = Centro comercial(tamanho de *p); se (!p) retorno -1; para (- Não. Eu... = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 0; Eu... < N; Eu...++) para (- Não. JJ = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 0; JJ < M; JJ++) (*p)Eu...Não.JJ] = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = Eu... + JJ; print_array(N, M, p); grátis(p); retorno 1;?

E aqui está uma implementação semelhante usando o recurso Auto VLA do C99:

- Não. Funk(- Não. N, - Não. M)( // Cuidado: verificações devem ser feitas para garantir N*M*sizeof(float) NÃO excede as limitações para VLAs automáticas e está dentro do tamanho disponível da pilha. flutuar pNão.NNão.M] // VLA automática é realizada na pilha, e tamanho quando a função é invocada para (- Não. Eu... = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 0; Eu... < N; Eu...++) para (- Não. JJ = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 0; JJ < M; JJ++) pNão.Eu...Não.JJ] = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = Eu... + JJ; // não há necessidade de livre (p) uma vez que desaparecerá quando a função sair, juntamente com o resto do frame da pilha retorno 1;?

Intercambialidade de ponteiro de array

A notação subscrita x[i] (onde x designa um ponteiro) é um açúcar sintático para *(x+i). Aproveitando o conhecimento do compilador sobre o tipo de ponteiro, o endereço para o qual x + i aponta não é o endereço base (apontado por x) incrementado por i bytes, mas é definido como o endereço base incrementado por i multiplicado pelo tamanho de um elemento para o qual x aponta. Assim, x[i] designa o elemento i+1do array.

Além disso, na maioria dos contextos de expressão (uma exceção notável é como operando de sizeof), uma expressão do tipo array é automaticamente convertida em um ponteiro para o primeiro elemento do array. Isso implica que uma matriz nunca é copiada como um todo quando nomeada como um argumento para uma função, mas apenas o endereço de seu primeiro elemento é passado. Portanto, embora as chamadas de função em C usem a semântica de passagem por valor, os arrays são, na verdade, passados por referência.

O tamanho total de um array x pode ser determinado aplicando sizeof a uma expressão do tipo array. O tamanho de um elemento pode ser determinado aplicando o operador sizeof a qualquer elemento não referenciado de uma matriz A, como em n = sizeof A[0]. Assim, o número de elementos em um array declarado A pode ser determinado como sizeof A / sizeof A[0]. Observe que, se apenas um ponteiro para o primeiro elemento estiver disponível, como costuma acontecer no código C devido à conversão automática descrita acima, as informações sobre o tipo completo da matriz e seu comprimento serão perdidas.

Gerenciamento de memória

Uma das funções mais importantes de uma linguagem de programação é fornecer recursos para gerenciar a memória e os objetos armazenados na memória. C fornece três maneiras principais de alocar memória para objetos:

  • Alocação de memória estática: espaço para o objeto é fornecido no binário no tempo de compilação; estes objetos têm uma extensão (ou vida) enquanto o binário que os contém é carregado na memória.
  • Alocação automática da memória: objetos temporários podem ser armazenados na pilha, e este espaço é automaticamente liberado e reutilizável após o bloco em que são declarados é saída.
  • Alocação de memória dinâmica: blocos de memória de tamanho arbitrário podem ser solicitados em tempo de execução usando funções de biblioteca, como malloc de uma região de memória chamada heap; estes blocos persistem até que posteriormente foram libertados para reutilização chamando a função da biblioteca realloc ou free

Essas três abordagens são apropriadas em diferentes situações e têm várias vantagens e desvantagens. Por exemplo, a alocação de memória estática tem pouca sobrecarga de alocação, a alocação automática pode envolver um pouco mais de sobrecarga e a alocação de memória dinâmica pode potencialmente ter uma grande sobrecarga para alocação e desalocação. A natureza persistente de objetos estáticos é útil para manter informações de estado em chamadas de função, a alocação automática é fácil de usar, mas o espaço de pilha é normalmente muito mais limitado e transitório do que a memória estática ou o espaço de heap, e a alocação de memória dinâmica permite a alocação conveniente de objetos cujas tamanho é conhecido apenas em tempo de execução. A maioria dos programas C faz uso extensivo de todos os três.

Onde possível, a alocação automática ou estática é geralmente mais simples porque o armazenamento é gerenciado pelo compilador, liberando o programador da tarefa potencialmente propensa a erros de alocar e liberar armazenamento manualmente. No entanto, muitas estruturas de dados podem mudar de tamanho em tempo de execução e, como as alocações estáticas (e alocações automáticas antes de C99) devem ter um tamanho fixo em tempo de compilação, há muitas situações em que a alocação dinâmica é necessária. Antes do padrão C99, arrays de tamanho variável eram um exemplo comum disso. (Consulte o artigo sobre malloc para obter um exemplo de arrays alocados dinamicamente.) Ao contrário da alocação automática, que pode falhar em tempo de execução com consequências descontroladas, as funções de alocação dinâmica retornam uma indicação (na forma de um valor do ponteiro) quando o armazenamento necessário não pode ser alocado. (A alocação estática muito grande geralmente é detectada pelo vinculador ou carregador, antes mesmo que o programa possa iniciar a execução.)

A menos que especificado de outra forma, os objetos estáticos contêm valores de ponteiro zero ou nulo na inicialização do programa. Os objetos alocados automática e dinamicamente são inicializados apenas se um valor inicial for explicitamente especificado; caso contrário, eles inicialmente têm valores indeterminados (normalmente, qualquer padrão de bit que esteja presente no armazenamento, que pode nem mesmo representar um valor válido para esse tipo). Se o programa tentar acessar um valor não inicializado, os resultados serão indefinidos. Muitos compiladores modernos tentam detectar e alertar sobre esse problema, mas podem ocorrer falsos positivos e falsos negativos.

A alocação de memória heap deve ser sincronizada com seu uso real em qualquer programa para ser reutilizada o máximo possível. Por exemplo, se o único ponteiro para uma alocação de memória heap sair do escopo ou tiver seu valor sobrescrito antes de ser desalocado explicitamente, essa memória não poderá ser recuperada para reutilização posterior e será essencialmente perdida para o programa, um fenômeno conhecido como vazamento de memória. Por outro lado, é possível que a memória seja liberada, mas é referenciada posteriormente, levando a resultados imprevisíveis. Normalmente, os sintomas de falha aparecem em uma parte do programa não relacionada ao código que causa o erro, dificultando o diagnóstico da falha. Esses problemas são amenizados em idiomas com coleta de lixo automática.

Bibliotecas

A linguagem de programação C usa bibliotecas como seu principal método de extensão. Em C, uma biblioteca é um conjunto de funções contidas em um único "arquivo" arquivo. Cada biblioteca normalmente possui um arquivo de cabeçalho, que contém os protótipos das funções contidas na biblioteca que podem ser usadas por um programa e declarações de tipos de dados especiais e símbolos de macro usados com essas funções. Para que um programa use uma biblioteca, ele deve incluir o arquivo de cabeçalho da biblioteca e a biblioteca deve estar vinculada ao programa, o que em muitos casos requer sinalizadores do compilador (por exemplo, -lm, abreviação de "vincular a biblioteca de matemática").

A biblioteca C mais comum é a biblioteca padrão C, que é especificada pelos padrões ISO e ANSI C e vem com todas as implementações C (implementações que visam ambientes limitados, como sistemas embarcados, podem fornecer apenas um subconjunto da biblioteca padrão). Essa biblioteca suporta entrada e saída de fluxo, alocação de memória, matemática, cadeias de caracteres e valores de tempo. Vários cabeçalhos padrão separados (por exemplo, stdio.h) especificam as interfaces para esses e outros recursos de biblioteca padrão.

Outro conjunto comum de funções da biblioteca C são aquelas usadas por aplicativos especificamente direcionados para sistemas Unix e semelhantes a Unix, especialmente funções que fornecem uma interface para o kernel. Essas funções são detalhadas em vários padrões, como POSIX e Single UNIX Specification.

Como muitos programas foram escritos em C, há uma grande variedade de outras bibliotecas disponíveis. As bibliotecas geralmente são escritas em C porque os compiladores C geram código-objeto eficiente; os programadores então criam interfaces para a biblioteca para que as rotinas possam ser usadas em linguagens de alto nível como Java, Perl e Python.

Manipulação de arquivos e streams

Entrada e saída de arquivos (E/S) não fazem parte da própria linguagem C, mas são manipuladas por bibliotecas (como a biblioteca padrão C) e seus arquivos de cabeçalho associados (por exemplo, stdio.h). A manipulação de arquivos geralmente é implementada por meio de E/S de alto nível, que funciona por meio de fluxos. Um fluxo é, dessa perspectiva, um fluxo de dados independente de dispositivos, enquanto um arquivo é um dispositivo concreto. A E/S de alto nível é feita por meio da associação de um stream a um arquivo. Na biblioteca padrão C, um buffer (uma área de memória ou fila) é usado temporariamente para armazenar dados antes de serem enviados ao destino final. Isso reduz o tempo gasto esperando por dispositivos mais lentos, por exemplo, um disco rígido ou uma unidade de estado sólido. As funções de E/S de baixo nível não fazem parte da biblioteca C padrão, mas geralmente fazem parte do "bare metal" programação (programação independente de qualquer sistema operacional, como a maioria das programações incorporadas). Com poucas exceções, as implementações incluem E/S de baixo nível.

Ferramentas de linguagem

Várias ferramentas foram desenvolvidas para ajudar os programadores C a encontrar e corrigir instruções com comportamento indefinido ou expressões possivelmente errôneas, com maior rigor do que o fornecido pelo compilador. O fiapo de ferramenta foi o primeiro, levando a muitos outros.

A verificação e auditoria automatizadas do código-fonte são benéficas em qualquer linguagem e, para C, existem muitas dessas ferramentas, como o Lint. Uma prática comum é usar o Lint para detectar códigos questionáveis quando um programa é escrito pela primeira vez. Depois que um programa passa pelo Lint, ele é compilado usando o compilador C. Além disso, muitos compiladores podem, opcionalmente, alertar sobre construções sintaticamente válidas que provavelmente serão erros. MISRA C é um conjunto proprietário de diretrizes para evitar esse tipo de código questionável, desenvolvido para sistemas embarcados.

Também existem compiladores, bibliotecas e mecanismos de nível de sistema operacional para executar ações que não são uma parte padrão do C, como verificação de limites para arrays, detecção de estouro de buffer, serialização, rastreamento dinâmico de memória e coleta automática de lixo.

Ferramentas como Purify ou Valgrind e vinculação com bibliotecas contendo versões especiais das funções de alocação de memória podem ajudar a descobrir erros de tempo de execução no uso da memória.

Usos

Justificativa para uso na programação de sistemas

A linguagem de programação C

C é amplamente usado para programação de sistemas na implementação de sistemas operacionais e aplicativos de sistemas embarcados. Isto é por várias razões:

  • O código gerado após a compilação não exige muitos recursos do sistema, e pode ser invocado a partir de algum código de inicialização de uma maneira simples – é simples de executar.
  • As declarações e expressões da linguagem C normalmente mapeiam bem em sequências de instruções para o processador de destino, e consequentemente há uma baixa demanda de tempo de execução nos recursos do sistema – é rápido de executar.
  • Com seu rico conjunto de operadores, a linguagem C pode utilizar muitos dos recursos de CPUs de destino. Onde uma CPU particular tem instruções mais esotéricas, uma variante de idioma pode ser construída com funções talvez intrínsecas para explorar essas instruções – pode usar praticamente todos os recursos da CPU alvo.
  • A linguagem facilita a sobreposição de estruturas em blocos de dados binários, permitindo que os dados sejam compreendidos, navegados e modificados – pode escrever estruturas de dados, até sistemas de arquivos.
  • A linguagem suporta um rico conjunto de operadores, incluindo manipulação de bits, para aritmética e lógica de inteiros, e talvez diferentes tamanhos de números de ponto flutuante – pode processar dados adequadamente estruturados efetivamente.
  • C é uma linguagem bastante pequena, com apenas um punhado de declarações, e sem muitos recursos que geram código alvo extenso – é compreensível.
  • C tem controle direto sobre a alocação de memória e a distribuição, o que dá eficiência razoável e tempo previsível para operações de manipulação de memória, sem quaisquer preocupações para esporádico parar o mundo eventos de coleta de lixo – tem desempenho previsível.
  • O hardware da plataforma pode ser acessado com ponteiros e pontuação do tipo, de modo que as características específicas do sistema (por exemplo, Registros de Controle/Status, registros de I/O) podem ser configuradas e usadas com o código escrito em C – ele interage bem com a plataforma em que está sendo executado.
  • Dependendo do linker e do ambiente, o código C também pode chamar bibliotecas escritas em linguagem de montagem, e pode ser chamado de linguagem de montagem – ele interopera bem com outro código de nível inferior.
  • C e suas convenções de chamada e estruturas de ligação são comumente usados em conjunto com outras línguas de alto nível, com chamadas tanto para C e de C suportados - ele interopera bem com outro código de alto nível.
  • C tem um ecossistema muito maduro e amplo, incluindo bibliotecas, quadros, compiladores de código aberto, depuradores e utilitários, e é o padrão de facto. É provável que os drivers já existam em C, ou que há uma arquitetura de CPU semelhante como um back-end de um compilador C, então há um incentivo reduzido para escolher outro idioma.

Uma vez usado para desenvolvimento web

Historicamente, C às vezes era usado para desenvolvimento web usando a Common Gateway Interface (CGI) como um "gateway" para obter informações entre o aplicativo da web, o servidor e o navegador. C pode ter sido escolhido em vez de idiomas interpretados por causa de sua velocidade, estabilidade e disponibilidade quase universal. Não é mais uma prática comum o desenvolvimento da Web ser feito em C, e existem muitas outras ferramentas de desenvolvimento da Web.

Algumas outras linguagens são escritas em C

Uma consequência da ampla disponibilidade e eficiência de C é que compiladores, bibliotecas e interpretadores de outras linguagens de programação são frequentemente implementados em C. Por exemplo, as implementações de referência de Python, Perl, Ruby e PHP são escritas em C.

Usado para bibliotecas com uso intensivo de computação

C permite que os programadores criem implementações eficientes de algoritmos e estruturas de dados, porque a camada de abstração do hardware é fina e sua sobrecarga é baixa, um critério importante para programas computacionais intensivos. Por exemplo, a GNU Multiple Precision Arithmetic Library, a GNU Scientific Library, Mathematica e MATLAB são totalmente ou parcialmente escritos em C. Muitos idiomas suportam funções de biblioteca de chamada em C, por exemplo, a estrutura baseada em Python NumPy usa C para o alto -desempenho e aspectos de interação de hardware.

C como linguagem intermediária

C às vezes é usado como uma linguagem intermediária por implementações de outras linguagens. Essa abordagem pode ser usada para portabilidade ou conveniência; usando C como uma linguagem intermediária, não são necessários geradores de código adicionais específicos da máquina. C tem alguns recursos, como diretivas de pré-processador de número de linha e vírgulas supérfluas opcionais no final das listas de inicializadores, que suportam a compilação do código gerado. No entanto, algumas das deficiências de C levaram ao desenvolvimento de outras linguagens baseadas em C especificamente projetadas para uso como linguagens intermediárias, como C--. Além disso, os principais compiladores contemporâneos GCC e LLVM apresentam uma representação intermediária que não é C, e esses compiladores suportam front-ends para muitos idiomas, incluindo C.

Aplicativos do usuário final

C também tem sido amplamente usado para implementar aplicativos de usuário final. No entanto, esses aplicativos também podem ser escritos em linguagens de nível superior mais recentes.

Limitações

o poder da linguagem de montagem e a conveniência de... linguagem de montagem

Dennis Ritchie

Embora C tenha sido popular, influente e extremamente bem-sucedido, ele tem desvantagens, incluindo:

  • O manuseio de memória dinâmica padrão com malloc e free é propenso a erros. Os erros incluem: A memória vaza quando a memória é alocada, mas não liberada; e o acesso à memória previamente liberada.
  • O uso de ponteiros e a manipulação direta da memória significa corrupção da memória é possível, talvez devido ao erro do programador, ou verificação insuficiente de dados ruins.
  • Há algum tipo de verificação, mas não se aplica a áreas como funções variáveis, e a verificação do tipo pode ser trivial ou inadvertidamente contornada. É fraco.
  • Uma vez que o código gerado pelo compilador contém poucas verificações em si, há uma carga sobre o programador para considerar todos os resultados possíveis, para proteger contra sobreposições de buffer, controle de limites de matriz, sobrecargas de pilha, exaustão de memória e considerar condições de corrida, isolamento de rosca, etc.
  • O uso de ponteiros e a manipulação em tempo de execução destes meios pode haver duas maneiras de acessar os mesmos dados (aliasing), que não é determinante no tempo de compilação. Isso significa que algumas otimizações que podem estar disponíveis para outras línguas não são possíveis em C. FORTRAN é considerado mais rápido.
  • Algumas das funções da biblioteca padrão, por exemplo. scanf ou strncat, pode levar a fugas de buffer.
  • Há padronização limitada em suporte a variantes de baixo nível em código gerado, por exemplo: diferentes convenções de chamada de função e ABI; diferentes convenções de embalagem de estrutura; diferentes ordenações de byte dentro de inteiros maiores (incluindo endianness). Em muitas implementações linguísticas, algumas dessas opções podem ser tratadas com a diretiva do pré-processador. #pragma, e alguns com palavras-chave adicionais, por exemplo, usar __cdecl Chamando convenção. Mas a directiva e as opções não são consistentemente apoiadas.
  • O manuseio de cordas usando a biblioteca padrão é intensivo de código, com gerenciamento de memória explícito necessário.
  • A linguagem não suporta diretamente a orientação de objetos, introspecção, avaliação de expressão em tempo de execução, genéricos, etc.
  • Existem poucos guardas contra o uso inadequado de características linguísticas, o que pode levar a um código inacessível. Esta facilidade de código complicado foi celebrado com competições como o Concurso Internacional de Código C Obfuscated e o Concurso de C Underhanded.
  • C não tem suporte padrão para o manuseio de exceção e oferece apenas códigos de retorno para verificação de erros. As funções de biblioteca padrão setjmp e longjmp foram usadas para implementar um mecanismo de busca por macros.

Para alguns propósitos, estilos restritos de C foram adotados, por ex. MISRA C ou CERT C, na tentativa de reduzir a oportunidade de bugs. Bancos de dados como o CWE tentam contar as maneiras como o C etc. tem vulnerabilidades, juntamente com recomendações para mitigação.

Existem ferramentas que podem atenuar algumas das desvantagens. Os compiladores C contemporâneos incluem verificações que podem gerar avisos para ajudar a identificar muitos erros potenciais.

Algumas dessas desvantagens levaram à construção de outras linguagens.

Idiomas relacionados

O gráfico de índice TIOBE, mostrando uma comparação da popularidade de várias linguagens de programação

C influenciou direta e indiretamente muitas linguagens posteriores, como C++, C#, D, Go, Java, JavaScript, Perl, PHP, Rust e shell C do Unix. A influência mais difundida foi sintática; todas as linguagens mencionadas combinam a sintaxe de declaração e (mais ou menos reconhecível) de expressão de C com sistemas de tipo, modelos de dados ou estruturas de programa em larga escala que diferem daquelas de C, às vezes radicalmente.

Existem vários interpretadores C ou near-C, incluindo Ch e CINT, que também podem ser usados para scripts.

Quando as linguagens de programação orientadas a objetos se tornaram populares, C++ e Objective-C eram duas extensões diferentes de C que ofereciam recursos orientados a objetos. Ambas as linguagens foram originalmente implementadas como compiladores fonte a fonte; o código-fonte foi traduzido para C e, em seguida, compilado com um compilador C.

A linguagem de programação C++ (originalmente denominada "C with Classes") foi criada por Bjarne Stroustrup como uma abordagem para fornecer funcionalidade orientada a objetos com uma sintaxe semelhante à do C. C++ adiciona maior força de digitação, escopo e outras ferramentas úteis na programação orientada a objetos e permite programação genérica por meio de modelos. Quase um superconjunto de C, o C++ agora suporta a maior parte do C, com algumas exceções.

Objetivo-C era originalmente muito "fino" camada sobre C, e continua sendo um superconjunto estrito de C que permite a programação orientada a objetos usando um paradigma híbrido de tipagem dinâmica/estática. Objective-C deriva sua sintaxe de C e Smalltalk: a sintaxe que envolve pré-processamento, expressões, declarações de funções e chamadas de funções é herdada de C, enquanto a sintaxe para recursos orientados a objetos foi originalmente retirada de Smalltalk.

Além de C++ e Objective-C, Ch, Cilk e Unified Parallel C são quase superconjuntos de C.

Contenido relacionado

HTTPS

Hypertext Transfer Protocol Secure é uma extensão do Hypertext Transfer Protocol ou, anteriormente, Secure Sockets Layer (SSL). O protocolo também é...

Euforia (linguagem de programação)

Euphoria é uma linguagem de programação criada por Robert Craig da Rapid Deployment Software em Toronto, Ontário, Canadá. Inicialmente desenvolvido no...

Debian

Debian também conhecido como Debian GNU/Linux, é uma distribuição Linux composta de software livre e de código aberto, desenvolvido pelo Projeto Debian...
Más resultados...
Tamaño del texto:
undoredo
format_boldformat_italicformat_underlinedstrikethrough_ssuperscriptsubscriptlink
save