2011-01-23 11 views
6

Si tengo una función que devuelve algún tipo de puntero, compruebo si hay errores estableciendo el retorno a NULL en caso de error.Error al verificar una función que devuelve un int

char *foo(void) { 
    //If stuff doesn't go okay 
    return NULL; 
} 

char *bar = foo(); 
if(!bar) return 1; 

Esto funciona muy bien porque sé que en tales casos nunca intentaré devolver NULL.

Sin embargo, a veces tendré funciones que devuelven enteros (en este caso, estoy leyendo entradas de un archivo de configuración). Sin embargo, el problema es que ahora no hay forma de verificar el valor devuelto por errores porque cualquier valor (incluido 0) podría ser genuino.

Un par de soluciones:

  1. incluir un parámetro de código de error en la función
  2. devolverá un código de error e incluyen un puntero int como un parámetro

El problema con estos dos es que tengo un conjunto de funciones que hacen lo mismo pero para diferentes tipos y quiero mantener una interfaz regular para que se puedan usar de la misma manera.

¿Existe alguna otra solución que no implique cambiar la interfaz a la función? ¿Cuál es la forma más común de lidiar con esta situación?

elegido la solución

Gracias por todos sus pensamientos y respuestas sobre esto.

Al final, decidí que si una función está destinada a devolver algunos datos, solo se puede devolver un error a través de un parámetro de error. De lo contrario, el error se devuelve directamente.

Elegí esta raíz porque generalmente encontré que al devolver formas de datos más complejas, el número de errores potenciales casi siempre era mayor que 1. Esto significaba que usar NULL como la única fuente de datos de error era poco práctico, ya que significaba que no había forma de determinar cuál era el error en realidad. Con funciones que devuelven datos como un int, también se volvió imposible distinguir múltiples códigos de error diferentes de los datos válidos.

Lo mismo no es cierto, por supuesto, para las funciones que realmente no devuelven ningún dato en cuyo caso soy libre de usar el valor de retorno como un código de error.

También me gusta el hecho de que el patrón anterior hace una clara distinción entre funciones que devuelven datos y funciones que no.

Respuesta

4

Existe la terrible solución de una variable global, análoga a errno. No recomendado.

Puede usar una función para recuperar la condición de error de la última llamada a una de las funciones en su conjunto; de nuevo, no se recomienda. Esto no es seguro para subprocesos, y es probable que comparta errores entre las funciones, en lugar de poder interrogar el último error de la función que usó (aunque eso podría solucionarse con un diseño apropiado).

Puede decidir que todas sus funciones devuelvan un estado que sea comprobable y el valor se devuelva mediante un argumento de puntero. Esto le da coherencia en todas las funciones, con un cambio en el patrón de uso.Escribirá consistentemente:

if ((rc = your_function(in1, in2, &out)) != 0) 
    ...handle error... 
else 
    ...use returned value... 

Esta es probablemente la solución menos desagradable.

La otra alternativa principal, pasar un puntero a una estructura de error o un puntero a un puntero a una estructura de error puede conducir a problemas de gestión de recursos. Se puede hacer que funcione si la estructura de error tiene un tamaño fijo para que desaparezca el problema de administración de recursos.

Error_t err; 

int result = your_function(in1, in2, &err); 
if (err.code != 0) 
    ...handle error... 
else 
    ...use returned value... 

No se puede probar la condición de error en la misma línea que se llama a la función, lo que algunos considerarían una molestia. Es más importante cuando se encuentra en la tercera cláusula 'else if' de una secuencia de operaciones; luego, debe introducir un nuevo nivel de anidación donde el código de error como valor de retorno no lo requiera. A su favor, la información en la estructura de error puede ser más completa que un solo número de error que podría conducir a mejores diagnósticos.

Puede utilizar una solución híbrida, un código de retorno de error más un parámetro de error para contener la información adicional para un diagnóstico mejorado. Sin embargo, raramente es elegido, AFAIK.

+0

Variable global puede hacerse mundial por hilo (TLS en Win32). – ruslik

+0

@ruslik: también puede usar TLS (almacenamiento local de subprocesos) en sistemas Unix; sin embargo, aún tiende a ser menos que completamente satisfactorio. –

+0

Sé que están disponibles en la mayoría de las plataformas, pero yo solo conocía el nombre de Windows. 'GetLastError()' usa este enfoque. – ruslik

1

Hay varias maneras de hacer lo que quiere, pero todas le harán tener una interfaz no homogénea (o en la lista una forma no homogénea para detectar errores).

Si no puede cambiar la firma de su función, puede usar una variable global que su función establezca cuando ocurra un error, pero creo que esta solución sería más sucia que cambiar la interfaz.

Otra solución más podría ser la de simulando excepciones de usando un salto o una señal de sistema, pero aún así no sugeriría esto (esto tampoco es portátil).

+0

La interfaz más homogénea es siempre devolver el código de error (con 0 para tener éxito), y devolver el resto de los valores por referencia. – ruslik

+0

@ruslik: sí, pero eso requiere cambiar todas sus funciones – peoro

3

La forma mejor y más común (practicada por la API de Windows, por ejemplo) es escribir todos los gastos de envío como este error:

int func (error_t * Error);

donde Error_t es algún tipo de indicación de error. De esa forma, no interferirá con el resultado devuelto.

2

Puede sacrificar el número más grande negativo o positivo para un int como INT_MAX. Simplemente cree un const MY_INT_ERROR_VALUE y compárelo.

+2

INT_MIN podría ser mejor; es asimétrico con INT_MAX de todos modos, y le deja con un rango de trabajo de -INT_MAX .. + INT_MAX que es simétrico con respecto a cero. En una máquina de dos complementos, INT_MIN tiene el patrón 0x8000, 0x80000000 o 0x8000000000000000 según el tamaño de un int (se muestran 2, 4, 8 bytes, suponiendo que conté correctamente). –

-1

1. Use NaNs, como coprocesadores flotantes. Elija un valor que nunca se haya usado como resultado para indicar un error.

# define VALUE_OF_FAIL (-99999) 

Entonces:

char foo(void) { 
    // If stuff doesn't go okay 
    return VALUE_OF_FAIL; 
} 

char bar = foo(); 
if (bar == VALUE_OF_FAIL) return 1; 

Una vez más, usted tiene que garantía que el resultado de un cálculo normal será nunca será VALUE_OF_FAIL. Elija un número de la periferia del rango entero. Puede definir más códigos de falla, pero luego debe escribir una función que verifique si una variable falla.

2. organizar el código de computación a una clase, a continuación, añadir isValid() método:

class FooBar { 

    public FooBar() { // constructor 
    this.fail = false; // initialize here... 
    } 

    char foo(void) { // the computation method 

    this.fail = false; // ...or here 

    // If stuff doesn't go okay 
    this.fail = true; // or error code 
    return 0; // any value 
    } 

    bool failed() { 
    return this.fail; 
    } 

} 

// outside the class  
fb = new FooBar(); 
char bar = fb->foo(); 
if (fb->failed()) return 1; 
+1

La mayoría de los compiladores de C no son compatibles con "clase", curiosamente. Se ve como una característica de C++. La pregunta está etiquetada C, y no C++. –

+0

-1: C, amigo. Además, el OP mencionó específicamente que cualquier valor de retorno podría ser válido en algunos casos. –

+0

Hola, tipos, es 2011, muéstrame un compilador de C que no es compatible con C++. O muéstrame un programador en C, que no usa C++ (err ... no lo hagas, no me pongas triste). – ern0

1

¿Qué tal esto:

void *foo(void *res) 
{ 
    // cast the void *res to whatever type the result would be 
    if (/* successfull */) 
     return res; 

    /* error */ 
    return NULL; 
} 

De esta manera usted tendrá la misma firma para todas aquellas funciones y también puede verificar fácilmente si hay errores.

+0

Heh, ¿no podría posiblemente hacerlo más genérico? Asumiré que esta publicación fue una broma. – Lundin

+0

Oh, puedes hacerlo más genérico: void * foo(); – Peyman

0

mi preferencia, al escribir código en un idioma que carece de manejo de excepciones, es utilizar siempre el valor de retorno como un indicador de error.

int get_value(int *value) 
{ 
    if (input_ok) 
     *value = input; 
     return 0; 
    return -1; 
} 

esto no parece práctico ya que obliga a utilizar variables intermedias para almacenar resultados de la función, pero resultó ser útiles tantas veces para detectar errores y manejarlos correctamente (nunca se puede confiar en la entrada del usuario).

Además, debe tener en cuenta que tener un valor especial para mostrar un error no es una buena idea: nunca se sabe qué sucederá con su código. Si luego desea agregar una nueva función y desafortunadamente el valor especial es útil para esta característica, ¿qué hará? escribir una nueva función con otro valor de error especial? ¿Qué pasa si la entrada debe cubrir todo el rango del tipo de retorno? Si esto es parte de un proyecto más grande, ¿cómo puede asegurarse de que cada programador que pueda usar su función tenga conocimiento del valor especial?

así que, por el lado seguro: no use un valor especial y use una ruta de manejo de errores dedicada.

nota que se puede revertir el código anterior y escribir int get_value(int *error);

Cuestiones relacionadas