Quais são algumas falhas que o deixam louco nas APIs C (incluindo bibliotecas padrão, bibliotecas de terceiros e cabeçalhos dentro de um projeto)? O objetivo é identificar as armadilhas do design da API em C, para que as pessoas que escrevem novas bibliotecas em C possam aprender com os erros do passado.
Explique por que a falha é ruim (de preferência com um exemplo) e tente sugerir uma melhoria. Embora sua solução possa não ser prática na vida real (é muito tarde para consertar strncpy
), ela deve ser um alerta para futuros escritores de bibliotecas.
Embora o foco desta pergunta sejam as APIs C, problemas que afetam sua capacidade de usá-los em outros idiomas são bem-vindos.
Por favor, dê uma falha por resposta, para que a democracia possa classificar as respostas.
fonte
malloc
string seria corrigida. Eu acho que dar um bom exemplo com a primeira resposta poderia realmente ajudar essa questão a prosperar. Obrigado!Respostas:
Funções com valores de retorno inconsistentes ou ilógicos. Dois bons exemplos:
1) Algumas funções do Windows que retornam um HANDLE usam NULL / 0 para um erro (CreateThread), outras usam INVALID_HANDLE_VALUE / -1 para um erro (CreateFile).
2) A função POSIX 'time' retorna '(time_t) -1' em erro, o que é realmente ilógico, pois 'time_t' pode ser um tipo assinado ou não assinado.
fonte
int time(time_t *out);
eBOOL CreateFile(LPCTSTR lpFileName, ..., HANDLE *out);
.Funções ou parâmetros com nomes não descritivos ou afirmativamente confusos. Por exemplo:
1) CreateFile, na API do Windows, na verdade não cria um arquivo, ele cria um identificador de arquivo. Ele pode criar um arquivo, exatamente como o 'open' pode, se solicitado através de um parâmetro. Este parâmetro possui valores chamados 'CREATE_ALWAYS' e 'CREATE_NEW' cujos nomes nem sequer sugerem sua semântica. ('CREATE_ALWAYS' significa que falha se o arquivo existe? Ou cria um novo arquivo em cima dele? 'CREATE_NEW' significa que ele cria sempre um novo arquivo e falha se o arquivo já existe? arquivo em cima dele?)
2) pthread_cond_wait na API POSIX pthreads, que apesar do nome, é uma espera incondicional .
fonte
pthread_cond_wait
não significa "espera condicional". Refere-se ao fato de que você está aguardando uma variável de condição .Tipos opacos que são transmitidos pela interface como identificadores excluídos do tipo. O problema é, obviamente, que o compilador não pode verificar o código do usuário para tipos de argumentos corretos.
Isso ocorre de várias formas e sabores, incluindo, entre outros:
void*
Abusousando
int
como um identificador de recurso (exemplo: a biblioteca CDI)argumentos digitados em sequência
Os tipos mais distintos (= não podem ser usados de forma totalmente intercambiável) são mapeados para o mesmo tipo excluído, o pior. Obviamente, o remédio é simplesmente fornecer ponteiros opacos tipicamente seguros ao longo das linhas de (exemplo C):
fonte
Funções com convenções de retorno de string inconsistentes e muitas vezes complicadas.
Por exemplo, getcwd solicita um buffer fornecido pelo usuário e seu tamanho. Isso significa que um aplicativo precisa definir um limite arbitrário no tamanho do diretório atual ou fazer algo assim ( do CCAN ):
Minha solução: retornar uma
malloc
string ed. É simples, robusto e não menos eficiente. Exceto plataformas embarcadas e sistemas mais antigos,malloc
é realmente muito rápido.fonte
snprintf(buf, 32, "%d", n)
, onde o comprimento da saída é previsível (certamente não superior a 30, a menos queint
seja realmente grande no seu sistema). De fato, o malloc não está disponível em muitos sistemas, mas para ambientes de desktop e servidor, é, e funciona muito bem.Funções que recebem / retornam tipos de dados compostos por valor ou que usam retornos de chamada.
Pior ainda, se esse tipo for uma união ou contiver campos de bits.
Do ponto de vista de um chamador em C, eles são realmente bons, mas eu não escrevo em C ou C ++, a menos que seja necessário, por isso costumo ligar através de um FFI. A maioria das FFIs não suporta uniões ou campos de bits, e algumas (como Haskell e MLton) não podem suportar estruturas passadas por valor. Para aqueles que podem lidar com estruturas de valor, pelo menos Common Lisp e LuaJIT são forçados a caminhos lentos - a Common Foreign Function Interface da Lisp deve fazer uma chamada lenta via libffi, e LuaJIT se recusa a JIT-compilar o caminho de código que contém a chamada. As funções que podem retornar aos hosts também acionam caminhos lentos no LuaJIT, Java e Haskell, com o LuaJIT não sendo capaz de compilar essa chamada.
fonte