9

Para empezar, permítanme decir que entiendo cómo y por qué puede suceder el problema que estoy describiendo. Estudié Ciencias de la Computación, y entiendo desbordamiento/subdesbordamiento y aritmética con o sin firma. (Para quienes no estén familiarizados con el tema, la Guía de codificación segura de Apple discusses integer overflow brevemente)¿La mejor manera de manejar e informar errores de asignación de memoria debido al desbordamiento de enteros en Objective-C?

Mi pregunta es acerca de informar y recuperarse de dicho error una vez que se ha detectado, y más específicamente en el caso de un marco Objective-C. (Escribo y mantengo CHDataStructures). Tengo unas pocas clases de colecciones que asignan memoria para almacenar objetos y expandirla dinámicamente según sea necesario. Todavía no he visto ningún bloqueo relacionado con el desbordamiento, probablemente porque mis casos de prueba utilizan en su mayoría datos sanos. Sin embargo, dados valores no validados, las cosas podrían explotar bastante rápido, y quiero evitar eso.

he identificado al menos dos casos comunes donde esto puede ocurrir:

  1. La persona que llama pasa un muy gran valor sin signo (o valor con signo negativo) a -initWithCapacity:.
  2. Se han agregado suficientes objetos para causar la capacidad de expandirse dinámicamente, y la capacidad ha crecido lo suficiente como para causar un desbordamiento.

La parte fácil es detectar si se producirá un desbordamiento. (Por ejemplo, antes de intentar asignar length * sizeof(void*) bytes, puedo verificar si length <= UINT_MAX/sizeof(void*), ya que si falla esta prueba significará que el producto se desbordará y potencialmente asignará una región de memoria mucho más pequeña que la deseada. En las plataformas que la admiten, la checkint.h API es otra alternativa.) La parte más difícil es determinar cómo tratar con gracia. En el primer escenario, la persona que llama está quizás mejor equipada (o al menos en la mentalidad) para lidiar con una falla. El segundo escenario puede ocurrir en cualquier parte del código que un objeto se agrega a la colección, que puede ser bastante no determinista.

Mi pregunta, entonces, es esta: ¿Cómo se espera que el "buen ciudadano" código Objective-C actúe cuando se produce un desbordamiento de entero en este tipo de situación? (Idealmente, dado que mi proyecto es un framework con el mismo espíritu que Foundation in Cocoa, me gustaría modelarlo de la manera en que se comporta para una "coincidencia de impedancias" máxima. La documentación de Apple que he encontrado no menciona mucho sobre esto.) Me imagino que, en cualquier caso, informar el error es un hecho. Dado que las API para agregar un objeto (que podría causar el escenario 2) no aceptan un parámetro de error, ¿qué puedo hacer realmente para ayudar a resolver el problema, en todo caso? ¿Qué se considera realmente correcto en tales situaciones? Soy reacio a escribir a sabiendas el código propenso a bloqueos si puedo hacerlo mejor ...

Respuesta

3

Hay dos temas en cuestión:

(1) Una asignación ha fracasado y que están fuera de la memoria.

(2) Ha detectado un desbordamiento u otra condición errónea que conducirá a (1) si continúa.

En el caso de (1), que está regado (a menos que la asignación no era tanto estúpida gran & que sabe que la asignación errónea era sólo que uno). Si esto sucede, lo mejor que puede hacer es bloquearse lo más rápido posible y dejar tanta evidencia como sea posible. En particular, la creación de una función que llame al abort() de un nombre como IAmCrashingOnPurposeBecauseYourMemoryIsDepleted() dejará evidencia en el registro de bloqueo.

Si es realmente (2), entonces hay preguntas adicionales. Específicamente, ¿puede recuperarse de la situación y, sin tener en cuenta, los datos del usuario aún están intactos? Si puede recuperarse, entonces grandioso ... hágalo y el usuario nunca tiene que saberlo.Si no, entonces usted necesita estar absolutamente seguro de que los datos del usuario no están corruptos. Si no es así, entonces guarda y muere. Si los datos del usuario están corruptos, haga lo mejor que pueda para no conservar los datos corruptos y dejar que el usuario sepa que algo ha salido terriblemente mal. Si los datos del usuario ya están persistentes, pero están corruptos, entonces ... bueno ... ay ... es posible que desee considerar la creación de una herramienta de recuperación de algún tipo.

+0

Afortunadamente, dado que el código en cuestión es un marco de colecciones de bajo nivel, realmente no puedo preocuparme específicamente por ningún dato de usuario, ya que no tengo idea de cómo persistirlo o verificar si hay corrupción. En los pocos lugares donde (2) podría ocurrir, planeo hacer algo inteligente, como intentar una asignación más pequeña (si es una colección en crecimiento) o devolver nada. En cualquier caso, ciertamente registraré evidencia para ayudar a rastrear el problema. Gracias por el útil contexto para hacerlo. –

3

Con respecto al almacenamiento dinámicamente creciente basado en arreglos, hay mucho que se puede hacer. Soy desarrollador en el programador de Moab para supercomputadoras, y también trabajamos con números muy grandes en sistemas con miles de procesadores, miles de trabajos y grandes cantidades de trabajo. En algún momento, no puede declarar que un búfer sea más grande, sin crear un tipo de datos completamente nuevo para tratar con tamaños superiores a UINT_MAX, o LONG_LONG_MAX, etc., en cuyo punto en la mayoría de las máquinas "normales" estará quedando sin espacio de pila/montón de todos modos. Así que diría registrar un mensaje de error significativo, evitar que la colección explote, y si el usuario necesita agregar muchas cosas a una colección de CHDataStructures, debe saber que hay problemas relacionados con números muy grandes, y quien llama debería verificar si el complemento fue exitoso (mantener un registro del tamaño de la colección, etc.).

Otra posibilidad es convertir el almacenamiento basado en arrays al almacenamiento dinámico asignado, basado en listas enlazadas, cuando llega al punto en que no se puede asignar una matriz más grande con un int sin signo o un largo sin signo. Esto sería costoso, pero sucedería con la suficiente frecuencia que no debería ser muy notable para los usuarios del framework. Dado que el límite del tamaño de una colección dinámicamente asignada basada en listas vinculadas es el tamaño del montón, cualquier usuario que agregue suficientes elementos a una colección para "desbordarlo" tendría mayores problemas que si su artículo fuera o no Agregado exitosamente.

4

Registre y presente una excepción.

Solo puede ser realmente un buen ciudadano para otros programadores, no para el usuario final, entonces pase el problema arriba y hágalo de una manera que explique claramente qué está pasando, cuál es el problema (proporcione números) y dónde está sucediendo, por lo que la causa raíz se puede eliminar.

+0

Esta es una buena respuesta, pero solo puedo elegir una, y la respuesta de @ bbum tiene detalles y contexto útiles adicionales. –

1

Yo diría que lo correcto sería hacer lo que hacen las colecciones de Cocoa. Por ejemplo, si tengo el siguiente código:

int main (int argc, const char * argv[]) { 
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 

    NSMutableArray * a = [[NSMutableArray alloc] init]; 

    for (uint32_t i = 0; i < ULONG_MAX; ++i) { 
     for (uint32_t i = 0; i < 10000000; ++i) { 
      [a addObject:@"foo"]; 
     } 
     NSLog(@"%lu rounds of 10,000,000 completed", i+1); 
    } 

    [a release]; 

    [pool drain]; 
    return 0; 
} 

..y simplemente se deja correr, con el tiempo morir con EXC_BAD_ACCESS. (Compilé y ejecuté esto como una aplicación de 32 bits, así que podría estar seguro de que se quedaría sin espacio cuando tocara 2 ** 32 objetos.

En otras palabras, lanzar una excepción sería agradable, pero no lo hago t pensar que realmente tiene que hacer nada.

0

el uso de afirmaciones y un controlador afirmación personalizada puede ser la mejor opción disponible para usted.

con afirmaciones, fácilmente podría tener muchos puntos de control en el código, donde se verifica que las cosas funcionan como deberían. Si no lo hacen, de forma predeterminada, la macro de aserción registra el error (cadena definida por el desarrollador) y lanza una excepción. También puede anular el comportamiento predeterminado utilizando un controlador de aserción personalizado e implementar un Manera de manejar las condiciones de error (incluso evitar arrojar excepciones).

Este enfoque permite un mayor grado de flexibilidad y puede modificar fácilmente su estrategia de manejo de errores (lanzando excepciones frente a tratar los errores internamente) en cualquier punto.

La documentación es muy concisa: Assertions and Logging.

Cuestiones relacionadas