2009-08-02 23 views
13

Vengo de un fondo de C#, pero estoy aprendiendo C por el momento. En C#, cuando uno quiere indicar que ha ocurrido un error, lanza una excepción. ¿Pero qué haces en C?C: ¿Cómo se simula una 'excepción'?

Digamos por ejemplo que tiene una pila con las funciones push y pop. ¿Cuál es la mejor manera de indicar que la pila está vacía durante un pop? ¿Qué devuelves de esa función?

double pop(void) 
{ 
    if(sp > 0) 
     return val[--sp]; 
    else { 
     printf("error: stack empty\n"); 
     return 0.0; 
    } 
} 

K&R 's ejemplo de la página 77 (el código de) devuelve un 0.0. Pero, ¿qué sucede si el usuario presionó 0.0 anteriormente en la pila, cómo sabe si la pila está vacía o si se devolvió un valor correcto?

Respuesta

15

comportamiento Excepción-como en C se lleva a cabo a través de setjmp/longjmp. Sin embargo, lo que realmente quieres aquí es un código de error. Si todos los valores son potencialmente recuperables, entonces es posible que desee tomar en un parámetro de salida como un puntero, y el uso que para devolver el valor, así:

int pop(double* outval) 
{ 
     if(outval == 0) return -1; 
     if(sp > 0) 
       *outval = val[--sp]; 
     else { 
       printf("error: stack empty\n"); 
       return -1; 
     } 
     return 0; 
} 

No es ideal, obviamente, pero tales son las limitaciones de C.

Además, si va por este camino, es posible que desee definir constantes simbólicas para sus códigos de error (o use algunos de the standard ones), para que un usuario pueda distinguir entre "pila vacía" y "me diste un puntero nulo, idiota ".

+2

No estoy de acuerdo, de alguna manera, porque incluso si entiendo lo que quieres decir, no daría a alguien que venga de java/C# land la suposición de que setjmp/longjmp es de alguna manera la 'solución' a '¿dónde está mi excepción?' – Jonke

+0

Tenga en cuenta que muy poco código C usa setjmp/longjmp por razones históricas. Los códigos de error son la forma C habitual. Los códigos de error no son muy buenos; la falta de Excepciones en C es una de las principales razones por las que los idiomas más modernos son mejores. – Nelson

+0

ANSI ha codificado 'setjmp 'por lo que está garantizado que funciona (al menos si el compilador cumple con el estándar), pero el cambio de pila y la concurrencia no tienen estandarizado , por lo que sigue siendo un problema escribir un paquete de hilos para C (incluso usando asm para hacer el cambio de contexto) porque un compilador * podría * (aunque podría ser improbable) realizar optimizaciones y transformar que rompen suposiciones en el paquete de subprocesos. – Jonke

4

Un enfoque es especificar que pop() tiene un comportamiento indefinido si la pila está vacía. Luego debe proporcionar una función is_empty() a la que se puede llamar para verificar la pila.

Otro enfoque es el uso de C++, que tiene excepciones :-)

+0

más útil para este caso particular, C++ tiene una pila allí mismo, en la biblioteca :-) –

+0

Pero el ++ pop std :: pila C la función en realidad no hace lo que el OP quiere. –

+1

Cierto, y entender por qué se agregará a la educación C++ del OP, que es el objetivo principal de la pregunta :-) De todos modos, al envolver una llamada a 'top()' y 'pop()', devolver una copia, da El mismo resultado final es tomar lo que tiene el OP y aplicar lo que dices sobre la necesidad de una función 'empty()'. IYSWIM. –

1

puede devolver un puntero a doble:

  • no NULL -> válida
  • NULL -> inválida
+0

downvotes sin comentarios son inútiles, por favor explique sus votos a la baja – dfa

+3

No soy el menos votado, pero me pregunto de dónde viene el almacenamiento de respaldo para el puntero. Si es el elemento reventado, la persona que llama debe desreferenciarlo antes de que se pueda insertar un nuevo valor. Si es un 'doble estático 'por separado, entonces la persona que llama tiene que quitar la referencia antes de la próxima llamada a pop. Ambos causan muchos problemas para el código de llamada. – RBerteig

+0

No perdoné tampoco, pero tenía las mismas preocupaciones. Creo que es un enfoque efectivo, pero necesitaría cambiar el funcionamiento de la función y almacenar datos para hacerlo. – Jon

7

Usted tiene algunas opciones:

1) Magia valor de error No siempre es lo suficientemente bueno, por la razón que describes. Supongo que en teoría para este caso podrías devolver un NaN, pero no lo recomiendo.

2) Defina que no es válido mostrar cuando la pila está vacía. Entonces su código simplemente asume que no está vacío (y no está definido si lo está), o lo afirma.

3) Cambiar la firma de la función para que pueda indicar el éxito o el fracaso:.

int pop(double *dptr) 
{ 
    if(sp > 0) { 
      *dptr = val[--sp]; 
      return 0; 
    } else { 
      return 1; 
    } 
} 

documento como "Si tiene éxito, devuelve 0 y escribe el valor a la ubicación a la que apunta DPTR En falla, devuelve un valor distinto de cero ".

Opcionalmente, puede usar el valor de retorno o errno para indicar el motivo de la falla, aunque para este ejemplo en particular solo hay una razón.

4) Pase un objeto "de excepción" a cada función con un puntero, y escriba un valor en caso de fallo. La persona que llama luego lo verifica o no de acuerdo a cómo usan el valor de retorno. Esto es muy parecido a usar "errno", pero sin que sea un valor de todo el hilo.

5) Como han dicho otros, implementar excepciones con setjmp/longjmp. Es factible, pero requiere pasar un parámetro extra en todas partes (el objetivo de longjmp para funcionar en caso de falla) u ocultarlo en globales. También hace que el manejo de recursos estilo C típico sea una pesadilla, porque no puedes llamar a nada que pueda saltar más allá de tu nivel de pila si tienes un recurso del cual eres responsable de liberarlo.

+0

+1: para el punto n. ° 3. –

10

Se puede construir un sistema de excepción en la parte superior de longjmp/setjmp: Exceptions in C with Longjmp and Setjmp. De hecho, funciona bastante bien, y el artículo es una buena lectura también. Así es como el código podría ser como si utilizó el sistema de excepción del artículo enlazado:

TRY { 
    ... 
    THROW(MY_EXCEPTION); 
    /* Unreachable */ 
    } CATCH(MY_EXCEPTION) { 
    ... 
    } CATCH(OTHER_EXCEPTION) { 
    ... 
    } FINALLY { 
    ... 
    } 

Es increíble lo que puede hacer con un poco de macros, ¿verdad? Es igualmente sorprendente lo difícil que es averiguar qué diablos está pasando si aún no sabes lo que hacen las macros.

longjmp/setjmp son portátiles: C89, C99 y POSIX.1-2001 especifican setjmp().

Nótese, sin embargo, que las excepciones aplicadas de esta manera todavía tendrán algunas limitaciones en comparación con excepciones "reales" en C# o C++. Un problema importante es que solo su código será compatible con este sistema de excepciones. Como no existe un estándar establecido para excepciones en C, el sistema y las bibliotecas de terceros simplemente no interoperarán de manera óptima con su sistema de excepción nacional. Aún así, esto a veces puede ser un truco útil.

No recomiendo usar esto en el código serio con el que se supone que trabajan otros programadores. Es demasiado fácil dispararse en el pie con esto si no sabes exactamente lo que está sucediendo. El enhebrado, la gestión de recursos y el manejo de señales son áreas problemáticas que los programas que no son de juguete encontrarán si intenta utilizar "excepciones" de longjmp.

+4

Realmente construí algo como esto en C++ antes de que las excepciones estuvieran ampliamente disponibles. Incluso lo implementé por propia forma de desenrollar la pila. Afortunadamente, volví a mis sentidos antes de usarlo en el código de producción. –

+0

@Neil: Me gustó la segunda parte de esa frase :-) – jeroenh

+1

"Afortunadamente, volví a mis sentidos". Felicitaciones. Symbian hizo lo mismo que tú, hasta el punto en que recuperó tus sentidos y lo enviaron. Más de 10 años después, todavía tienen NewLC en todas partes ... –

2

No hay un equivalente a excepciones en C recta usted tiene que diseñar su firma función para devolver información de error, si eso es lo que quiere.

Los mecanismos disponibles en C son:

GOTOS
  • no locales con setjmp/longjmp
  • Señales

Sin embargo, ninguno de éstos tiene una semántica remotamente parecido a C# (o C++) excepciones .

3

En casos como este, que usualmente lo hace uno de

  • dejar en manos de la persona que llama. p.ej. le toca a la persona que llama saber si es seguro pop() (por ejemplo, llamar a una función stack-> is_empty() antes de abrir la pila), y si la persona que llama se equivoca, es su culpa y buena suerte.
  • Señala el error a través de un parámetro out o devuelve un valor.

por ejemploO haces

double pop(int *error) 
{ 
    if(sp > 0) { 
     return val[--sp]; 
     *error = 0; 
    } else { 
    *error = 1; 
     printf("error: stack empty\n"); 
     return 0.0; 
    } 

}

o

int pop(double *d) 
{ 
    if(sp > 0) { 
     *d = val[--sp]; 
     return 0; 
    } else { 
    return 1; 

    } 
} 
0

Ya hay algunas buenas respuestas aquí, sólo quería hablar de que algo parecido a "excepción", se puede hacer con el uso de una macro, como se ha hecho en el increíble MinUnit (esto solo devuelve la "excepción" a la función de llamador).

1

1) Usted devuelve un valor de indicador para mostrar que falló, o usa una sintaxis TryGet donde el resultado es un booleano para tener éxito mientras el valor pasa a través de un parámetro de salida.

2) Si se trata de Windows, hay una forma de excepciones de C en el nivel del sistema operativo, llamada Structured Exception Handling, que utiliza la sintaxis como "_try". Lo menciono, pero no lo recomiendo para este caso.

4

Esto en realidad es un ejemplo perfecto de los males de intentar sobrecargar el tipo de retorno con valores mágicos y simplemente un diseño de interfaz cuestionable.

Una solución que podría utilizar para eliminar la ambigüedad (y por lo tanto la necesidad de "excepción como el comportamiento") en el ejemplo es definir un adecuado tipo de retorno:

struct stack{ 
    double* pData; 
    uint32 size; 
}; 

struct popRC{ 
    double value; 
    uint32 size_before_pop; 
}; 

popRC pop(struct stack* pS){ 
    popRC rc; 
    rc.size=pS->size; 
    if(rc.size){ 
     --pS->size; 
     rc.value=pS->pData[pS->size]; 
    } 
    return rc; 
} 

Uso de curso es:

popRC rc = pop(&stack); 
if(rc.size_before_pop!=0){ 
    ....use rc.value 

Esto sucede todo el tiempo, pero en C++ para evitar este tipo de ambigüedades uno por lo general sólo devuelve un

std::pair<something,bool> 

donde el bool es un indicador de éxito - vistazo a algunos de:

std::set<...>::insert 
std::map<...>::insert 

Alternativamente añadir un double* a la interfaz y devolver un código de retorno (n UNOVERLOADED), dicen que una enumeración que indica el éxito.

Por supuesto, no es necesario devolver el tamaño en la estructura popRC. Podría haber sido

enum{FAIL,SUCCESS}; 

Pero puesto que el tamaño podría servir como un indicio útil para el pop'er que también podría utilizarlo.

Por cierto, todo corazón de acuerdo en que la interfaz de pila estructura debe tener

int empty(struct stack* pS){ 
    return (pS->size == 0) ? 1 : 0; 
} 
0

setjmp, longjmp y macros. Se ha realizado varias veces —, la implementación más antigua que conozco es la de Eric Roberts y Mark vanderVoorde —, pero la que uso actualmente es parte de Dave Hanson's C Interfaces and Implementations y es gratuita de Princeton.

1

Algo que nadie ha mencionado todavía, es bastante fea sin embargo:

int ok=0; 

do 
{ 
    /* Do stuff here */ 

    /* If there is an error */ 
    break; 

    /* If we got to the end without an error */ 
    ok=1; 

} while(0); 

if (ok == 0) 
{ 
    printf("Fail.\n"); 
} 
else 
{ 
    printf("Ok.\n"); 
} 
Cuestiones relacionadas