2010-09-01 5 views
10

La siguiente es la implementación más popular de strcpy en sistemas tradicionales. ¿Por qué dest y src no están marcados para NULL al principio? Escuché una vez que en los viejos tiempos la memoria era limitada, por lo que siempre se prefería el código corto. ¿Implementarás strcpy y otras funciones similares con las comprobaciones de puntero NULL al inicio en estos días? Por qué no?Por qué no hay controles de cordura en legado strcpy()

char *strcpy(char *dest, const char *src) 
{ 
    char *save = dest; 
    while(*dest++ = *src++); 
    return save; 
} 
+3

Puede ser más seguro en general. Pero eso también significa que los desarrolladores experimentados tienen que pagar el costo de la seguridad (que no necesitan) solo para que los desarrolladores inexpertos no se arrimen. –

+0

Los desarrolladores con experiencia tienen que pagar el costo de la seguridad (que no creen que necesitan) ... (FTFY). –

+1

@Brian Hooper: no, si usa C, debe saber exactamente lo que necesita. Mi código incrustado nunca, nunca necesita verificaciones 'NULL' en' strcpy' porque todos los buffers están asignados estáticamente y se usan directamente. No hay forma de que pase 'NULL' a' strcpy'. Entonces, ¿por qué querría pagar el precio? No hay "no creo que necesite" al respecto. – detly

Respuesta

19

NULL es un mal indicador, pero también lo es (char*)0x1. ¿Debería también verificar eso? En mi opinión (no sé la razón definitiva por la cual), los controles de cordura en una operación de bajo nivel son innecesarios. strcpy() es tan fundamental que debe tratarse como una instrucción asm, y debe hacer sus propios controles de cordura en la persona que llama si es necesario. Solo mis 2 centavos :)

+0

Acepto: las rutinas de bajo nivel deben implementarse para mayor eficiencia y las rutinas de alto nivel deben agregar seguridad cuando corresponda. –

+2

+1 para señalar que 'NULL' es solo un ejemplo del 99,9% del espacio del puntero que probablemente tampoco sea válido. –

+1

¿Qué te hace decir (char *) 0x1 es necesariamente un mal puntero? En C99, el puntero nulo * es * un caso especial en el sentido de que "se garantiza que no se puede comparar con un puntero a cualquier objeto o función". (6.3.2.3). – JeremyP

3

La razón más probable es: Debido a que strcpy no está especificado para trabajar con las entradas NULL (es decir, su comportamiento en este caso no está definido).

Entonces, ¿qué debe hacer un implementador de la biblioteca si se pasa un NULL? Yo diría que lo mejor es dejar que la aplicación falle. Piénselo de esta manera: un bloqueo es una señal bastante obvia de que algo salió mal ... Ignorar silenciosamente una entrada NULL, por otro lado, puede enmascarar un error que será mucho más difícil de detectar.

+1

No. 'strcpy' en la entrada NULL es un comportamiento indefinido, que puede bloquearse, o puede * silenciosamente hacer lo correcto *. Ciertamente no puede confiar en un error de tiempo de ejecución al usar 'strcpy' con NULL. –

+0

Podría ser, pero la realidad es que no lo hará. – Puppy

+0

@Philip: Buen punto: he editado la respuesta para eliminar la afirmación errónea de que "lo correcto es bloquearse" (pero todavía diría que es lo mejor que se puede hacer). –

2

Las comprobaciones NULL no se implementaron porque los primeros objetivos de C admitían protecciones fuertes de memoria. Cuando un proceso intentaba leer o escribir en NULL, el controlador de memoria indicaba a la CPU que se intentó un acceso a la memoria fuera de rango (violación de la segmentación), y el kernel eliminaría el proceso ofensivo.

Esta fue una respuesta correcta, porque el código que intenta leer o escribir en un puntero NULL está roto; la única respuesta es volver a escribir el código para verificar los valores de devolución de malloc(3) y amigos y tomar medidas correctivas. En el momento en que intenta utilizar punteros a la memoria no asignada, es demasiado tarde para tomar una decisión correcta sobre cómo solucionar la situación.

11

Todo el lenguaje C está escrito con el lema "Nos comportaremos correctamente siempre que el programador sepa lo que está haciendo". Se espera que el programador sepa hacer todos los controles que necesita hacer. No es sólo la comprobación de NULL, es asegurar que dest puntos a suficiente memoria asignada para mantener src, se comprueba el valor de retorno de fopen para asegurarse de que el archivo realmente hicieron abierta con éxito, saber cuándo memcpy es seguro y cuando se requiere memmove, y así.

Obtener strcpy para comprobar NULL no cambiará el paradigma del idioma. Deberá asegurarse de que dest apunta a suficiente espacio, y esto es algo que strcpyno puede verificar sin cambiar la interfaz. También deberá asegurarse de que src es '\0' -terminados, lo que nuevamente strcpy no puede verificar.

Hay algunas funciones de biblioteca estándar C que hacen marque NULO: por ejemplo, free(NULL) es siempre seguro. Pero en general, C espera que sepas lo que estás haciendo.

[C++ generalmente evita la biblioteca <cstring> a favor de std::string y amigos.]

+0

+1 para std :: string –

+0

..que es tan incompatible, parece estar * diseñado para lastimar *. – peterchen

15

No hay controles de cordura porque una de las ideologías subyacentes más importantes de C es que el desarrollador proporciona la cordura. Cuando asumes que el desarrollador está cuerdo, terminas con un lenguaje que puede usarse para hacer cualquier cosa, en cualquier lugar.

Esto no es un objetivo explícitamente establecido - es muy posible que alguien presente una implementación que haga verifique esto, y más. Tal vez lo hayan hecho. Pero dudo que muchas personas acostumbradas a C clamen por usarlo, ya que tendrían que poner los controles de todos modos si hubiera alguna posibilidad de que su código se transportara a una implementación más habitual.

+11

* ... el desarrollador proporciona la cordura. * - Me gusta;) – caf

0

Debería pensar que la biblioteca estándar C funciona como la capa de abstracción más fina posible sobre el código de ensamblaje que no desea generar para que su material salga por la puerta. Todo más allá de eso, como la comprobación de errores, es su responsabilidad.

0

Según mi opinión, cualquier función que desee definir tendrá una condición previa y una condición posterior. Cuidar las condiciones previas nunca debe ser parte de una función. Lo siguiente es una condición previa para usar Strcpy tomado de la página del manual.

La función strcpy() copia la cadena apuntada por src (incluido el carácter de terminación '\ 0') a la matriz apuntada por dest. Las cadenas no se pueden superponer, y el destino de la cadena de destino debe ser lo suficientemente grande para recibir la copia.

Ahora bien, si la condición previa no se cumple entonces las cosas podrían ser indefinido.

Si incluiría un cheque NULL en mi Strcpy ahora. Preferiría tener otra safe_strcpy, dando prioridad a la seguridad. Definitivamente incluiría controles NULL y manejaría las condiciones de desbordamiento. Y en consecuencia, mi condición previa se modifica.

6
  1. Por lo general es mejor para la biblioteca para que la persona que llama decidir lo que quiere la semántica caso de no ser. ¿Qué tendría strcpy hacer si cualquiera de los argumentos es NULL? Silenciosamente no hacer nada? ¿Falló un assert (que no es una opción en versiones sin depuración)?

  2. Es más fácil optar por lo que es para darse de baja. Es trivial escribir su propio contenedor alrededor de strcpy que valida las entradas y usar eso en su lugar. Sin embargo, si la biblioteca hiciera esto por sí misma, no habría forma de elegir no realizar esos controles, salvo la reimplementación de strcpy. (Por ejemplo, es posible que ya sepa que los argumentos que pasa al strcpy no son NULL, y podría ser algo que le preocupe si lo llama en un ciclo cerrado o si le preocupa minimizar el consumo de energía). En general, es mejor equivocarse del lado de otorgar más libertad (incluso si esa libertad viene con responsabilidad adicional).

+0

+1 para el envoltorio personalizado que implementa su política de manejo de errores. (Aunque probablemente no me envuelva strcpy individualmente. Yo uso una clase StrOnBuf que envuelve las rutinas básicas de manipulación de memoria intermedia de caracteres, y se puede configurar para truncar en silencio, con truncar aserción de depuración, o tirar). – peterchen

+0

+1 El punto 1 es la respuesta a la pregunta de OP. –

0

Simplemente no hay ningún error semántico definido para ello. En particular, no hay forma de que strcpy devuelva un valor de error. C99 simplemente afirma:

strcpy La función devuelve el valor de s1.

Por lo tanto, para una implementación conforme no habría posibilidad de devolver la información de que algo salió mal. Entonces, ¿por qué molestarse con eso?

Todo esto es voluntario, creo, ya que strcpy se sustituye por la mayoría de los compiladores de ensamblador muy eficiente directamente. Las verificaciones de error dependen de la persona que llama.

+0

Como el comportamiento no está definido si se pasa 'NULL',' strcpy' podría devolver algo que no sea 's1' cuando' s1' es 'NULL'. O podría no regresar en absoluto (bloqueo o ciclo infinito). –

+0

¿Qué pasa si usted es el primero y se le pidió que diseñara Strcpy desde cero y también escribir los estándares C99 usted mismo. ¿Lo cambiarías para devolver algún valor de error? – user436748

+0

@ user436748: desafortunadamente esto es puramente hipotético, tres opciones. Para un diseño como "alto nivel", solo le pediría que devuelva 'NULL' si se produce un error, para establecer' errno' con una indicación y también que los datos originales no se modifican en tal caso. esto se hace en varios otros lugares, pero no aquí para 'strcpy'. Si lo diseñara como de "bajo nivel", me limitaría a "hacer lo correcto" pero, además, le pediría que produzca una segfault en caso de que uno de los punteros sea 'NULL'. Entonces, preguntaste, para C99 real, podrías ir por 'char * strcpy (char s1 [static 1], char const s2 [static 1]);' –