2009-03-09 9 views
7

El siguiente es un extracto del libro de Bjarne Stroustrup, The C++ Programming Language:¿Cómo limitar el impacto de las características de lenguaje dependientes de la implementación en C++?

Sección 4.6:

Algunos de los aspectos de los tipos fundamentales de C++ s, tales como el tamaño de un int, son implementación- definido (§C.2). Señalo estas dependencias y, a menudo, recomiendo evitarlas o tomar medidas para minimizar su impacto. ¿Por qué deberías molestarte? Las personas que programan en una variedad de sistemas o usan una variedad de compiladores se preocupan mucho porque si no lo hacen, se ven obligados a perder el tiempo encontrando y corrigiendo errores desconocidos. Las personas que afirman que no les importa la portabilidad generalmente lo hacen porque usan un solo sistema y sienten que pueden permitirse la actitud de que "el lenguaje es lo que mi compilador implementa". Esta es una visión estrecha y miope. Si su programa es un éxito, es probable que sea portado, por lo que alguien tendrá que encontrar y solucionar problemas relacionados con las características dependientes de la implementación. Además, los programas a menudo necesitan ser compilados con otros compiladores para el mismo sistema, e incluso una versión futura de su compilador favorito puede hacer algunas cosas de manera diferente a la actual. Es mucho más fácil conocer y limitar el impacto de las dependencias de implementación cuando se escribe un programa que tratar de desenredar el desorden posterior.

Es relativamente fácil limitar el impacto de las funciones del lenguaje dependientes de la implementación.

Mi pregunta es: ¿Cómo limitar el impacto de las características del lenguaje dependientes de la implementación? Por favor, mencione las características del lenguaje dependientes de la implementación y luego muestre cómo limitar su impacto.

Respuesta

4

algunas ideas:

  • Desafortunadamente tendrá que usar macros para evitar algunos problemas específicos específicos del compilador o plataforma. Se puede ver en las cabeceras de las bibliotecas Boost para ver que se puede conseguir con bastante facilidad engorroso, por ejemplo vistazo a los archivos:

  • Los tipos enteros tienden a ser desordenados entre las diferentes plataformas, tendrá que definir sus propios tipos o usar algo como Boost cstdint.hpp

  • Si decide utilizar cualquier biblioteca, a continuación, hacer una verificación de que la biblioteca está soportado en la plataforma dada

  • Utilice las bibliotecas con un buen apoyo y soporte de la plataforma claramente documentado (por ejemplo Boost)

  • Puede abstraerse de algunos problemas específicos de implementación C++ al depender en gran medida de bibliotecas como Qt, que proporcionan una "alternativa" en el sentido de tipos y algoritmos. También intentan hacer que la codificación en C++ sea más portátil. ¿Funciona? No estoy seguro.

  • No todo se puede hacer con macros. Su sistema de compilación deberá ser capaz de detectar la plataforma y la presencia de ciertas bibliotecas.Muchos sugerirían autotools para la configuración del proyecto, por el contrario recomiendo CMake (lenguaje bastante agradable, no más M4)

  • orden de bits y la alineación podría ser un problema si usted hace algo de intromisión bajo nivel (es decir, reinterpret_cast y amigos cosas parecidas (amigos era una mala palabra en el contexto C++)).

  • arroje un montón de banderas de advertencia para el compilador, para gcc Recomendaría al menos -Wall -Wextra. Pero hay mucho más, consulte la documentación del compilador o este question.

  • tiene que tener cuidado con todo lo que está definido por la implementación y depende de la implementación. Si quieres la verdad, solo la verdad, nada más que la verdad, entonces ve a la norma ISO.

+0

Buena respuesta. Más cosas de las que sabía :) – workmad3

4

Bueno, los tamaños de variable uno mencionados son un problema bastante conocido, con la solución común de proporcionar versiones de tipos de tipos básicos que tienen tamaños bien definidos (normalmente anunciados en el nombre typedef). Esto se hace usando macros de preprocesador para dar visibilidad de código diferente en diferentes plataformas. Ej .:

#ifdef __WIN32__ 
typedef int int32; 
typedef char char8; 
//etc 
#endif 
#ifdef __MACOSX__ 
//different typedefs to produce same results 
#endif 

Otros problemas se resuelven normalmente de la misma manera también (es decir, usando tokens preprocesador para llevar a cabo la compilación condicional)

+0

O podría diseñar su código para que no le importen cuáles son los tamaños reales. Tal vez agregue una afirmación para asegurarse de que sean lo suficientemente grandes. A menos que esté interactuando con controladores de bajo nivel, la mayoría del código se puede hacer para que sea independiente de los tamaños de contacto. – KeithB

+0

Los tamaños no son normalmente un problema con un código bien escrito, cierto ... pero los rangos son con frecuencia. Podrías usar afirmaciones y cosas como std :: numerical_limits, o puedes usar typedesffed cosas como las anteriores y tener tamaños y rangos bien definidos para tipos 'estándar' :) – workmad3

2

Una buena solución es utilizar encabezamientos comunes que definen tipos typedeff'ed como necesario.

Por ejemplo, incluir sys/types.h es una forma excelente de manejar esto, al igual que el uso de bibliotecas portátiles.

3

La dependencia de implementación más obvia es el tamaño de los tipos enteros. Hay muchas formas de manejar esto. La manera más obvia es utilizar typedef para crear enteros de los distintos tamaños:

typedef signed short int16_t; 
typedef unsigned short uint16_t; 

El truco aquí es elegir una convención y se adhieren a ella. Qué convención es la parte difícil: INT16, int16, int16_t, t_int16, Int16, etc. C99 tiene el archivo stdint.h que usa el estilo int16_t. Si su compilador tiene este archivo, úselo.

Del mismo modo, debe ser pedante sobre el uso de otra norma define como size_t, time_t, etc.

El otro truco es saber cuándo no utilizar estos typedef. Una variable de control de bucle utilizada para indexar una matriz, debería simplemente tomar tipos enteros sin procesar para que la compilación genere el mejor código para su procesador. para (int32_t i = 0; i < x; ++ i) podría generar una gran cantidad de código innecesario en un procesador de 64 bits, al igual que usar int16_t sería en un procesador de 32 bits.

+0

De hecho, el mejor tipo para usar para iterar a través de una matriz en cualquier plataforma donde stdint.h está disponible será offset_t. ese tipo es siempre el tipo correcto para el puntero aritmático – SingleNegationElimination

+0

Sabía que había un tipo para eso, pero no estaba seguro de qué. size_t tiene sentido, pero no tiene sentido :) Gracias por esa información. – jmucchiello

1

Una de las principales formas de evitar la dependencia de determinados tamaños de datos es leer los datos persistentes & escritura como texto, no binario. Si se deben usar datos binarios, entonces todas las operaciones de lectura/escritura se deben centralizar en unos pocos métodos y enfoques como los de los que ya se describen aquí.

Un segundo paso que puede hacer es habilitar todas las advertencias de sus compiladores. por ejemplo, usar el -pedantic flag con g ++ le advertirá sobre muchos posibles problemas de portabilidad.

+0

En realidad, modularizar su código es una buena práctica, no se limita a usar datos binarios. – Arafangion

+0

Por supuesto, no quise sugerir lo contrario, pero mucha gente rocía lecturas y escrituras en todo su código. –

0

Si le preocupa la portabilidad, cosas como el tamaño de una int pueden determinarse y tratarse sin mucha dificultad. Muchos compiladores C++ también son compatibles con las características C99 como los tipos int: int8_t, uint8_t, , uint32_t, etc. Si el suyo no los admite de forma nativa, siempre puede incluir <cstdint> o <sys/types.h>, que, con frecuencia, tiene typedef ed. <limits.h> tiene estas definiciones para todos los tipos básicos.

La norma solo garantiza el tamaño mínimo de un tipo, en el que siempre puede confiar: sizeof(char) < sizeof(short) <= sizeof(int) <= sizeof(long). char debe tener al menos 8 bits. short y int deben tener al menos 16 bits. long debe tener al menos 32 bits.

Otras cosas que podrían ser definidos aplicación incluyen la ABI y formatos de nombre-mangling (comportamiento del export "C++" específicamente), pero a menos que usted está trabajando con más de un compilador, que por lo general no sea un problema.

2

Hay dos métodos para esto:

  • definir sus propios tipos con un tamaño conocido y utilizarlos en lugar de los tipos predefinidos (como typedef int int32 # si-ed para varias plataformas)
  • técnicas de uso que no dependen del tamaño tipo

el primero es muy popular, sin embargo, la segunda, cuando sea posible, por lo general resulta en un código más limpio. Esto incluye:

  • no asuma puntero puede ser echado a int
  • , no asuma que sepa el tamaño en bytes de los tipos individuales, siempre utilice sizeof para comprobarlo
  • al guardar los datos en archivos o transfiriéndolos a través de la red, utilizar técnicas que son portables a través de cambiar el tamaño de datos (como el ahorro de archivos de texto/carga)

Un ejemplo reciente de esto es la escritura de código que puede ser compilado para ambas plataformas x86 y x64. La parte peligrosa aquí es el puntero y el tamaño size_t; prepárese, puede ser 4 u 8 dependiendo de la plataforma, al convertir o puntero de diferenciación, nunca lanzar a int, usar intptr_t y tipos typedef-ed similares en su lugar.

0

El siguiente es también un extracto del libro de Bjarne Stroustrup, The C++ Programming Language:

Sección 10.4.9:

No hay garantías independientes de la implementación se hacen sobre el orden de construcción de objetos no locales en diferentes unidades de compilación. Por ejemplo:

// file1.c: 
    Table tbl1; 
// file2.c: 
    Table tbl2; 

Si tbl1 se construye antes TBL2 o viceversa es dependiente de la implementación. El orden ni siquiera está garantizado para ser reparado en cada implementación particular. La vinculación dinámica, o incluso un pequeño cambio en el proceso de compilación, puede alterar la secuencia. El orden de destrucción es similarmente dependiente de la implementación.

Un programador puede garantizar la inicialización adecuada mediante la implementación de la estrategia que las implementaciones suelen emplear para objetos estáticos locales: un primer cambio.Por ejemplo:

class Zlib { 
    static bool initialized; 
    static void initialize() { /* initialize */ initialized = true; } 
public: 
    // no constructor 

    void f() 
    { 
     if (initialized == false) initialize(); 
     // ... 
    } 
    // ... 
}; 

Si hay muchas funciones que necesitan para poner a prueba el interruptor por primera vez, esto puede ser tedioso, pero a menudo es manejable. Esta técnica se basa en el hecho de que los objetos asignados estáticamente sin constructores se inicializan en . El caso realmente difícil es aquel en el que la primera operación puede ser de tiempo crítico, por lo que la sobrecarga de las pruebas y la posible inicialización pueden ser graves. En ese caso, se requieren más trucos (§21.5.2).

Un enfoque alternativo para un objeto simple es presentarlo como una función (§9.4.1):

int& obj() { static int x = 0; return x; } // initialized upon first use 

interruptores por primera vez no manejan todas las situaciones imaginables. Por ejemplo, es posible crear objetos que se refieran entre sí durante la construcción. Es mejor evitar tales ejemplos. Si tales objetos son necesarios, deben construirse cuidadosamente por etapas.

Cuestiones relacionadas