¿Cuáles son algunos defectos que te vuelven loco en las API de C (incluidas las bibliotecas estándar, las bibliotecas de terceros y los encabezados dentro de un proyecto)? El objetivo es identificar las dificultades de diseño de API en C, para que las personas que escriben nuevas bibliotecas en C puedan aprender de los errores del pasado.
Explique por qué el defecto es malo (preferiblemente con un ejemplo) e intente sugerir una mejora. Aunque es posible que su solución no sea práctica en la vida real (es demasiado tarde para solucionarla strncpy
), debería avisar a futuros escritores de bibliotecas.
Aunque el foco de esta pregunta son las API de C, los problemas que afectan su capacidad de usarlos en otros lenguajes son bienvenidos.
Dé una falla por respuesta, para que la democracia pueda clasificar las respuestas.
fuente
malloc
cadena 'd lo solucionaría. Creo que dar un buen ejemplo con la primera respuesta realmente podría ayudar a que esta pregunta prospere. ¡Gracias!Respuestas:
Funciones con valores de retorno inconsistentes o ilógicos. Dos buenos ejemplos:
1) Algunas funciones de Windows que devuelven un HANDLE usan NULL / 0 para un error (CreateThread), algunas usan INVALID_HANDLE_VALUE / -1 para un error (CreateFile).
2) La función POSIX 'time' devuelve '(time_t) -1' en caso de error, lo cual es realmente ilógico ya que 'time_t' puede ser un tipo con signo o sin signo.
fuente
int time(time_t *out);
yBOOL CreateFile(LPCTSTR lpFileName, ..., HANDLE *out);
.Funciones o parámetros con nombres no descriptivos o afirmativamente confusos. Por ejemplo:
1) CreateFile, en la API de Windows, en realidad no crea un archivo, crea un identificador de archivo. Puede crear un archivo, al igual que la lata 'abierta', si se le solicita a través de un parámetro. Este parámetro tiene valores llamados 'CREATE_ALWAYS' y 'CREATE_NEW' cuyos nombres ni siquiera insinúan su semántica. (¿'CREATE_ALWAYS' significa que falla si el archivo existe? ¿O crea un nuevo archivo encima? ¿'CREATE_NEW' significa que siempre crea un nuevo archivo y falla si el archivo ya existe? ¿O crea uno nuevo? archivo encima de él?)
2) pthread_cond_wait en la API POSIX pthreads, que a pesar de su nombre, es una espera incondicional .
fuente
pthread_cond_wait
no significa "condicionalmente esperar". Se refiere al hecho de que está esperando una variable de condición .Tipos opacos que se pasan a través de la interfaz como identificadores de tipo eliminado. El problema es, por supuesto, que el compilador no puede verificar el código de usuario para los tipos de argumento correctos.
Esto viene en varias formas y sabores, que incluyen, entre otros:
void*
abusoutilizando
int
como un manejador de recursos (ejemplo: la biblioteca CDI)argumentos escritos a máquina
Los tipos más distintos (= no se pueden usar de manera totalmente intercambiable) se asignan al mismo tipo de tipo eliminado, peor. Por supuesto, el remedio es simplemente proporcionar punteros opacos de tipo seguro a lo largo de las líneas de (ejemplo C):
fuente
Funciones con convenciones de retorno de cadena inconsistentes y a menudo engorrosas.
Por ejemplo, getcwd solicita un búfer proporcionado por el usuario y su tamaño. Esto significa que una aplicación tiene que establecer un límite arbitrario en la longitud actual del directorio o hacer algo como esto ( desde CCAN ):
Mi solución: devolver una
malloc
cadena ed. Es simple, robusto y no menos eficiente. Exceptuando plataformas integradas y sistemas más antiguos, enmalloc
realidad es bastante rápido.fuente
snprintf(buf, 32, "%d", n)
, donde la longitud de salida es predecible (ciertamente no más de 30, a menos queint
sea realmente enorme en su sistema). De hecho, malloc no está disponible en muchos sistemas, pero sí lo es para entornos de escritorio y servidor, y funciona realmente bien.Funciones que toman / devuelven tipos de datos compuestos por valor, o que usan devoluciones de llamada.
Peor aún si dicho tipo es una unión o contiene campos de bits.
Desde la perspectiva de una persona que llama en C, en realidad están bien, pero no escribo en C o C ++ a menos que sea necesario, por lo que generalmente llamo a través de una FFI. La mayoría de las FFI no admiten sindicatos o campos de bits, y algunos (como Haskell y MLton) no pueden admitir estructuras pasadas por valor. Para aquellos que pueden manejar estructuras por valor, al menos Common Lisp y LuaJIT se ven obligados a rutas lentas: la interfaz de función externa común de Lisp debe hacer una llamada lenta a través de libffi, y LuaJIT se niega a compilar JIT la ruta de código que contiene la llamada. Las funciones que pueden volver a llamar a los hosts también activan rutas lentas en LuaJIT, Java y Haskell, ya que LuaJIT no puede compilar dicha llamada.
fuente