Unir sus variables: una introducción a los tipos de datos avanzados en C

Programar C sin variables es como, bueno, programar C sin variables. Son tan esenciales para el lenguaje que ni siquiera necesitan una analogía aquí. Podemos declararlos y usarlos con tanta furia como queramos, pero a menudo tiene sentido tener un poco más de estructura y combinar datos que pertenecen juntos en una colección común. Los conjuntos son un buen comienzo para combinar datos similares, especialmente cuando no hay un significado específico del índice de la matriz que no sea la posición del valor, pero tan pronto como desee una asociación más significativa de cada valor, las matrices se volverán restrictivas. Y son inútiles si desea combinar diferentes tipos de datos. Afortunadamente, C nos proporciona alternativas adecuadas listas para usar.

Esta entrada presentará estructuras y unions en C, cómo declararlos y usarlos, y cómo unions puede usarse (ab) como un enfoque alternativo para punteros y operaciones binarias.

Estructuras

Antes de bucear unions, sin embargo, comenzaremos con un tipo de variable común más común: el struct. A struct es una colección de una cantidad arbitraria de variables de cualquier tipo de datos, incluidos otros structs, envuelto como un tipo de datos personalizado. Digamos que queremos almacenar tres enteros de 16 bits que representan los valores de temperatura, humedad y sensor de luz.

Sí, podríamos usar una matriz, pero siempre debemos recordar qué índice representa qué valor, mientras que con un struct, podemos darle a cada valor su propio identificador. Para asegurarnos de que terminamos con una variable entera de 16 bits sin nombre, independientemente del sistema a continuación, usaremos las definiciones típicas de la biblioteca estándar C de stdint.h.

#include <stdint.h>

struct sensor_data {
    uint16_t temperature;
    uint16_t humidity;
    uint16_t brightness;
};

Ahora tenemos un nuevo tipo de datos que contiene tres enteros dispuestos uno al lado del otro en la memoria. Declaremos una variable de este nuevo tipo y asignemos valores a cada uno de los structcampo.

struct sensor_data data;

data.temperature = 123;
data.humidity    = 456;
data.brightness  = 789;

Alternativas, las struct se puede inicializar directamente mientras se declara. C ofrece dos formas diferentes de hacer esto: pretender que es una matriz o usar inicializadores seleccionados. Tratarlo como una matriz asigna cada valor a la subvariable en el mismo orden que el struct Fue definido. Los inicializadores seleccionados se pueden asignar arbitrariamente por nombre. Una vez iniciado, podemos acceder a cada campo individual de la misma manera que le asignamos valores.

struct sensor_data array_style = {
    123, /* temperature */
    456, /* humidity    */
    789  /* brightness  */
};

struct sensor_data designated_initializers = {
    .humidity    = 456,
    .temperature = 123,
    .brightness  = 789
};

printf("Temperature: %dn", array_style.temperature);
printf("Humidity:    %dn", array_style.humidity);
printf("Brightness:  %dn", array_style.brightness);

Observe cómo los campos en los inicializadores seleccionados no están en su orden original, e incluso podríamos omitir campos individuales y dejarlos simplemente sin inicializar. Esto nos permite modificar el struct luego, sin preocuparse mucho por ajustar cada lugar al que estaba acostumbrado, a menos que, por supuesto, cambiemos el nombre o eliminemos un campo.

Bitfields

El mapa de bits es un caso especial struct esto nos permite dividir una parte de un número entero en su propia variable de longitud de bits arbitraria. Para permanecer con el sensor de datos de muestra, supongamos que cada valor del sensor es leído por un convertidor de analógico a digital (ADC) con una resolución de 10 bits.

Mantener los resultados en números enteros de 16 bits, por lo tanto, desperdiciará 6 bits por cada valor que sea más de un tercio. El uso de campos de bits nos permitirá usar un único entero de 32 bits y dividirlo en tres variables de 10 bits, dejando solo 2 bits completamente sin usar.

struct sensor_data_bitfield {
    uint32_t temperature:10;
    uint32_t humidity:10;
    uint32_t brightness:10;
};

También podríamos agregar un cuarto campo de 2 bits de ancho para usar el espacio restante sin costo adicional. Y esto se sabe casi todo sobre mapas de bits. Además de agregar la longitud de bits, los campos de bits siguen siendo solo structs, por lo que se les trata como si fueran un cliente habitual struct. Los campos de bits pueden depender un poco de la arquitectura y el compilador, por lo que se necesita cierta precaución.

Sindicatos

Lo que nos lleva al tema de hoy que a menudo se pasa por alto, union. Desde afuera se ven y se comportan de la misma manera struct, y de hecho se declaran, se rubrican y se accede de la misma manera. Así que gira el nuestro struct sensor_data en un union, solo tenemos que cambiar la palabra clave y listo.

union sensor_data {
    uint16_t temperature;
    uint16_t humidity;
    uint16_t brightness;
};

Sin embargo, contrario a un struct, los campos dentro union no están organizados en orden secuencial en la memoria, sino que están todos ubicados en la misma dirección. Haz un struct sensor_data la variable comienza en la dirección de memoria 0x1000, la temperature El campo estará ubicado en 0x1000, la humidity campo en 0x1010, y el brightness campo en la dirección 0x1020. Con union, los tres campos estarán ubicados en una dirección 0x1000.

Lo que esto significa en la práctica se muestra fácilmente después de que asignamos valores a todos los campos, como hicimos en el struct ejemplo anterior.

union sensor_data data;

data.temperature = 123;
data.humidity    = 456;
data.brightness  = 789;

printf("Temperature: %dn", data.temperature);

A diferencia del struct por ejemplo, el valor impreso aquí no será el valor asignado 123, pero 789 en lugar de. Dado que cada campo en el union comparte exactamente la misma ubicación de memoria, siempre que uno de los campos obtiene un valor, se sobrescriben todos los valores asignados previamente de otros campos. Es por eso que rara vez tiene sentido tener campos con el mismo tipo de datos en union, sino que mezcla diferentes tipos. Tenga en cuenta que los tamaños de los tipos de datos no necesitan coincidir, por lo que no es un problema tener union con, por ejemplo, un entero de 32 bits y uno de 8 bits, el valor de 8 bits simplemente se trunca si es necesario. El tamaño de la union en sí será igual al tamaño del campo más grande, por lo que con un entero de 32 y 8 bits, el union tendrá un tamaño de 4 bytes.

Usando Uniones

A union básicamente le da a una ubicación de memoria diferentes nombres y en consecuencia diferentes tamaños. Puede parecer un concepto extraño, pero veamos cómo se puede utilizar para acceder fácilmente a diferentes bytes individuales dentro de un tipo de datos más largo.

union data_bytes {
    uint32_t data;
    uint8_t bytes[4];
};

Aquí tenemos un entero de 32 bits que coincide con una matriz de cuatro enteros de 8 bits. Si asignamos un valor al 32 bits data campo y leer un lugar del bytes matriz, podemos extraer eficazmente cada byte individual de la data campo.

union data_bytes db;
db.data = 0x12345678;
printf("0x%02xn", db.bytes[1]);

La salida real dependerá de si la arquitectura de su procesador es de gama baja o alta. Las arquitecturas de gama pequeña interpretarán el índice de la tabla 1 como el segundo entero menos byte significativo 0x56, mientras que las arquitecturas de gama alta lo interpretarán como el segundo entero más byte significativo 0x34.

El mismo principio utilizado para extraer un byte también funciona al revés, y podemos usar unions para unir enteros. Consideremos un ejemplo del mundo real del convertidor de analógico a digital del ATmega328. El ADC tiene una resolución de 10 bits y, al mirar sus registros, el valor convertido se almacena en dos registros separados de 8 bits: ADCL y ADCH para el byte inferior y superior respectivamente. A struct con dos campos con el nombre de esos dos registros parece una buena opción para esto, y dado que también queremos el valor completo de 10 bits de la conversión, usaremos el struct junto con un entero de 16 bits dentro de un union.

union adc_data {
    struct {
        uint8_t adcl;
        uint8_t adch;
    };
    uint16_t value;
};

Como puede ver, el struct no tiene un nombre de tipo ni tiene el campo en sí un nombre que nos permita acceder a los campos dentro del struct como si fueran parte del union mem.

union adc_data adc;

adc.adch = ADCH;
adc.adcl = ADCL;

printf("0x%04xn", adc.value);

Tenga en cuenta que acceder al struct Los campos funcionarán de forma anónima solo mientras no haya conflictos de nombres. Si hay nombres de campo duplicados, el struct en sí mismo requerirá un nombre de campo. Yo soy la struct tiene su propio identificador, también podemos agregar un nombre típico al struct en sí mismo, lo que nos permite utilizarlo también fuera del union.

union adc_data {
    struct register_map {
        uint8_t adcl;
        uint8_t adch;
    } registers;
    uint16_t value;
};

union adc_data adc;
struct register_map adc_registers;

adc.registers.adch = ADCH;
adc.registers.adcl = ADCL;
printf("0x%04xn", adc.value);

Una vez almacenados los valores de registro en el struct campos, podemos leer el valor completo del campo "valor" de 16 bits. Por supuesto, no es necesario union para combinar esos dos valores de registro, también podríamos simplemente usar una diapositiva sesgada y una operación OR:

printf("0x%04xn", (ADCH << 8) | ADCL);

Es cierto que en realidad no es nada único unions. No importa cómo los use, puede lograr lo mismo con operaciones personalizadas o con punteros. Pero es precisamente esta equivalencia lo que les interesa.

Atajos con sindicatos

Echemos un vistazo al ejemplo anterior de extracción de bytes y veamos qué otras opciones tenemos para generar un byte de un entero. Como recordamos, tuvimos union con un entero de 32 bits y un conjunto de cuatro enteros de 8 bits:

union data_bytes {
    uint32_t data;
    uint8_t bytes[4];
};

La forma más común de extraer partes de cualquier valor es combinar cambios de bits con una operación Y, sin embargo, en este caso particular, también podemos dar el valor de 32 bits a una serie de valores de 8 bits. Bueno, implementemos todas estas opciones y veamos qué aspecto tienen.

uint32_t value = 0x12345678;
union data_bytes db;
db.data = value;

// shift one byte to the right and extract the LSB
printf("0x%02xn", (value >> 8) & 0xff);
// cast to uint8_t pointer, access it as an array
printf("0x%02xn", ((uint8_t *) &value)[1]);
// cast to uint8_t pointer, access via pointer arithmetic
printf("0x%02xn", *(((uint8_t *) &value) + 1));
// simply take the union field
printf("0x%02xn", db.bytes[1]);

Mirando más de cerca los punteros, básicamente estamos diciendo que todo lo que se encuentra en la dirección de memoria del valor de 32 bits es en realidad una colección de valores de 8 bits. Ahora, aplicando la misma terminología al union declaración, básicamente estamos diciendo que todo está ubicado en el unionLa dirección de memoria es uno de 32 bits o cuatro valores de 8 bits, al igual que podemos hacer con la conversión, excepto que con union, seremos muy explícitos cuál de esos dos tipos será cuando nos acerquemos al valor. Iusencia unions ofrece un acceso directo a las conversiones de tipos de datos, al tiempo que garantiza que los datos en sí se utilicen de manera significativa y válida en su contexto, y que el compilador lo honre. Podrías decirlo unions es para punteros lo enums es a un conjunto de constantes de preprocesador.

Mirando números de punto flotante

Tengamos otro ejemplo e investiguemos los números de punto flotante, los números de punto flotante de precisión simple IEEE 754 para ser precisos, también conocidos como float. Si alguna vez se ha preguntado cómo se ve un flotante para una CPU, piense que es un número entero. Obviamente no en un "Lanzar int para flotar para eliminar facción" camino, pero en "Formato sin formato IEEE 754 binary32" camino.

union float_inspection {
    float floatval;
    uint32_t intval;
} fi;

float f = 65.65625;
fi.floatval = f;

printf("0x%08xn", fi.intval);
// ..or then again with pointers
printf("0x%08xn", *((uint32_t *) &f));

Ambos saldrán 0x42835000 que no nos dirá mucho sin estudiar a fondo el formato binary32, que es una combinación de signo, exponente, y fracción valor con ancho de bit estandarizado. Recordando el concepto de un campo de bits, podemos extender el union con struct, ayudándonos a separar el formato binary32. Para completar, los mismos datos también se extraen mediante operaciones de bits como nounion alternativa.

union float_inspection {
    float floatval;
    uint32_t intval;
    struct {
        uint32_t fraction:23;
        uint32_t exponent:8;
        uint32_t sign:1;
    };
} fi;

float f = 65.65625;
uint32_t i = *((uint32_t *) &f);
fi.floatval = f;

printf("%d %d 0x%xn", fi.sign, fi.exponent, fi.fraction);
printf("%d %d 0x%xn", (i >> 31), ((i >> 23) & 0xff), (i & 0x7fffff));

Dejaré que usted decida qué opción es más clara de leer y más fácil de mantener. De cualquier manera la salida nos dará un valor de señal. 0, exponente 133y la fracción 0x35000. Siguiendo la definición del formato, podemos construir el número de coma flotante inicial 65.65625 volver de ella. Entonces, si alguna vez termina analizando un volcado de datos sin procesar o un blob binario y encuentra un valor de punto flotante, ahora sabe cómo usar union para saber qué número representa.

Esta es toda la gente

Hay dos cosas más de las que preocuparse al usar unions buscar dentro de otros tipos de datos: indigenidad y alineación. La mayoría de las computadoras y microcontroladores son indios de gama baja, pero tenga cuidado con las arquitecturas Motorola 68k y AVR32, que son indias grandes. Por razones de rendimiento, a varios procesadores también les gusta alinear la memoria en límites de 2 o 4 bytes, lo que puede significar que dos uint8_ts puede haber cuatro bytes en la memoria. En GCC, puede utilizar el aligned atributo para controlar este comportamiento, pero puede estar sujeto a una sanción por exceso de velocidad y está fuera del alcance de este artículo.

Esto termina nuestra expedición en structarena unions. Con suerte, podríamos brindarle nuevos conocimientos e ideas sobre cómo organizar sus variables y algunas alternativas útiles para lidiar con ellas. Háganos saber si puede pensar en otras formas de usar todo esto, y en qué formas extrañas lo ha usado o encontrado. unions antes.

  • romano dice:

    El tercer fragmento de código, que se imprime al final: ¿son tonterías esos "0x"?

    • Erik Johnson dice:

      En particular, los primeros 2 tipos se imprimen en decimal, luego el último está en hexadecimal, por lo que para mantener la salida clara, prefieren el hexadecimal con 0x

    • Melvin dice:

      Precisamente porque printf no formatea los datos como hexadecimal

    • Sven Gregori dice:

      Oh, tienes razón, tenía un sexto número impreso allí en un borrador anterior y me olvidé de ajustarlo.
      Arreglado ahora, gracias por señalarlo.

  • F dice:

    Usar una estructura sin nombre dentro de un sindicato es un truco inteligente.

  • sbrk dice:

    Los sindicatos se diseñaron para ahorrar espacio, utilizando la misma memoria para almacenar dos o más tipos diferentes de datos. No se utilizaron para extraer bytes, mordiscos o bits, ni para arrojar datos implícitamente. El uso de sindicatos para hacer esto no es seguro porque el compilador tiene una amplia latitud para llevar a cabo el almacenamiento real como quiera.

    Abusar de los sindicatos de esta manera es insoportable y probablemente resultará en horas divertidas de búsqueda de errores.

    • jpa dice:

      La mayoría de las plataformas tienen una especificación ABI bastante rígida, por lo que el compilador no tiene mucha libertad sobre cómo organizarla. Pero sí, el estándar C en sí no está garantizado aquí.

      • sbrk dice:

        Más de una vez me han mordido estos rasgos tentadores.

        Como consejo: use el tamaño de registro nativo [unsigned] int siempre que no se trate de un rango de valores que sea exactamente 2 ^ sizeof (corto) o 2 ^ sizeof (char). La verificación de intervalos al comienzo de su API tomará muchos menos ciclos que el código de máquina generado y los ciclos de bus para manejar valores de tamaño inferior a un registro. Puede pensar que está ahorrando espacio, pero hoy no vale la pena.

    • Joel dice:

      Si comprende por qué el relleno de una alineación estructural causa portabilidad en una articulación, entonces puede ser útil.
      La mayoría de la gente de C / C ++ también evitará el uso de arado directo en línea y, cuando sea necesario, completará un resumen con un comentario significativo que explique por qué se hizo. Casi todos los MCU modernos que he usado tendrán un subconjunto específico de macros especiales para lidiar con excentricidades específicas de la plataforma.

      El kit de herramientas gcc a veces puede generar binarios no optimizados en algunas plataformas (a veces esta característica también es útil), pero es compatible con la mayoría de los procesadores modernos de manera bastante consistente. Estoy realmente agradecido de que la industria haya decidido adoptar un compilador estándar no oficial después de 35 años de laboriosos compiladores. =)

    • bueyes dice:

      Y, sin embargo, este truco se utilizó tanto en Windows (https://msdn.microsoft.com/en-us/library/windows/desktop/ms738571(v=vs.85).aspx) como en versiones anteriores de BSD.

    • Julian Skidmore dice:

      Acordado.

  • snarkysparky dice:

    Enviar un puntero a la estructura para obtener mucha información donde se necesita es un truco agradable y elegante

  • Adán dice:

    Es importante recordar que las uniones en C ++ no siguen la convención de C. Después de escribir en un campo de unión, acceder a los otros campos es un comportamiento indefinido (aunque la mayoría de los traductores los implementarán además de C).

  • MattAtHazmat dice:

    Tenga mucho cuidado al utilizar campos de bits: cuando un campo de bits se superpone a una unidad de almacenamiento de límites, el comportamiento se lleva a cabo definido ALTERNATIVAMENTE: NO PORTÁTIL.

  • default_ex dice:

    Para aquellos que usan C #, puede lograr una unión con una estructura explícita. Marque la estructura con un atributo StructLayout usando el parámetro LayoutKind.Explicit y luego aplique los atributos FieldOffset a cada campo. El atributo Field Offset acepta un argumento sobre cuántos bytes desde el principio de la estructura compensan ese campo. No hay alineación a nivel de bits como C / C ++, pero incluso la alineación a nivel de bytes es muy poderosa. Solo tenga cuidado durante su pedido inicial, ya que la regla C sobre cada campo de estructura que se inicializará todavía se aplica para la estructura explícita.

  • Breettull dice:

    Esta parece ser una buena manera de hacer pesadillas para cualquiera que intente llevar su código a otra plataforma algún día. (¡incluido usted mismo posible!)

    • F dice:

      No es una plataforma más, tuve problemas terribles con la alineación estructural en Solaris cuando intenté usar gcc para crear un código que simplemente se negaba a compilar correctamente con Sun CC. Los pequeños programas de prueba pueden ahorrarle horas de rascarse la cabeza cuando intenta depurar esto.

  • Sr. Nada dice:

    Hago esto con mucho código de serialización, pero debes tener cuidado de transportarlos entre diferentes arquitecturas. cómo transferir la estructura de la mcu a la computadora, era necesario cambiar un poco en un extremo para decodificar los datos correctamente.

  • mímica dice:

    Nunca he visto campos de bits en C. ¿Alguien puede dar un ejemplo de qué IDE para microcontroladores (PIC, AVR, MSP430) admite esto?

    • Moryc dice:

      Utilizo campos de bits en una estructura con XC8 en MPLAB X. Es una excelente manera de mantener indicadores de estado que tienen menos de 8 bits. Esta es también la forma en que un compilador hace posible usar los nombres de campos de registros de un solo bit ...

  • HarveyBallWanger dice:

    Como programador que no es de C, eso se ve horrible. Sin embargo, el tipo de artículo "aquí hay una oportunidad" siempre es divertido de leer.

    • F dice:

      Si está escribiendo en otro idioma y necesita descomprimir las estructuras entregadas por código C, definitivamente necesitará aprender a hacerlo.

  • Smeeg dice:

    Se puede aprender mucho sobre un hombre por la forma en que pronuncia "sindicalizado".

    • Tim Trzepacz dice:

      ¿No ionizado?
      Unio-Ized?
      ¿Uni-Oni-Zed?
      Union-Nized?

      • Rodney McKay dice:

        cepo-izita

    • Jonathan dice:

      Y además lo {mal} deletrea 🙂

  • pardobsso dice:

    Buen articulo.
    Una cosa (ya mencionada aquí) que ocasionalmente me pica es cómo se empaquetan las estructuras. Este de Eric Raymond trata muchos casos extraños con más detalle: The Lost Art of C Structure Packing

  • Tim Trzepacz dice:

    Algunos comentarios:
    A mucha gente no le gustan los mapas de bits porque no hay ninguna especificación sobre cómo el compilador los empaquetará, por lo que su código puede no funcionar correctamente en otros sistemas. Como dice el refrán, "se necesita cierta precaución".
    Tampoco les gusta porque la gente espera que el compilador haga un código menos eficiente para manejarlos de lo que pueden codificar manualmente. Tal vez esa preocupación se haya disipado en esta era de procesadores ultrarrápidos, pero para los microcontroladores imagino que todavía importa.
    Finalmente, como se especifica, solo funcionan con elementos de tamaño int. Algunos compiladores pueden funcionar más allá de la especificación para permitir el empaquetado de caracteres sin nombre, ints cortos, etc., pero no puede confiar en ellos.
    Muchos programadores los evitan y siguen haciendo operaciones de bits explícitas para saber que su código siempre funcionará.
    Me gustan los mapas de bits porque hacen que el código sea mucho más legible, pero tengo que ser muy consciente de cómo los uso si alguna vez tengo la intención de llevar el código a cualquier otra plataforma.

    También gracias por informarme sobre los "principiantes seleccionados" en C. ¡Aprendí mucho antes de C99 y no sabía que eran un problema!

  • burla dice:

    Esto me recuerda a la cláusula REDEFINES en Cobol, pero en C parece ser solo codificadores confusos y generación de errores.
    Tal vez porque C es solo un lenguaje ensamblador mal escrito. Es una broma ...
    Ejemplo en Cobol:
    05 FOTO 9999.
    05 B REDEFINE IMAGEN 9V999.
    05 C REDEFINS FIC 99V99.

    ¡Esto es claramente legible y al menos no depende de la compilación!

    • Jonathan dice:

      Quizás, pero depende de si escribes en una pizarra o llenas tu rostro 🙂

  • ian dice:

    Solo he estado programando en C durante poco más de 40 años (todavía tengo una copia original de K&R en el estante) :-), y generalmente diría que si usa la función 'unirse', o no no sé lo que estás haciendo b) tu programa es malo, oc) ambos.

    Si desea tratar un bloque de memoria como un bloque de memoria, utilice un bloque de memoria. Si desea utilizar una variable de tipo, utilice una. Si desea convertirlo de uno a otro, hágalo a propósito, de modo que lo justifique en el contexto que está utilizando.

    Unirse a "union" solo causará errores, efectos secundarios y código difícil de mantener.

    • Moryc dice:

      Por favor, Sage, infórmame sobre cómo puedo resolver el siguiente problema correctamente:

      Escribo para micro de 8 bits para que la memoria sea accesible por bytes individuales. Tengo tres entradas cortas que contienen 3 valores de calibre de 16 bits de ancho. Quiero guardarlos en una EEPROM interna, pero solo puedo escribir 8 bits a la vez y tengo que leerlos en el mismo orden después de reiniciar. Así que los empaqueté en una estructura y lo puse junto con un conjunto de 6 caracteres. Luego puedo escribir un primer carácter de una matriz a una primera dirección EEPROM, luego un segundo a un segundo, y así sucesivamente. Lo leo en el mismo orden.

      Oh, Sage, enséñame la forma correcta de hacerlo. Iluminar min. Muéstrame el camino ...

      • Redhatter (VK4MSL) dice:

        Espero que WordPress no abuse de esto ...


        struct config_t {
        uint16_t cal_x;
        uint16_t cal_y;
        uint16_t cal_z;
        };

        int eeprom_write (size_t además, uint8_t byte);

        int save (const struct config_t * const cfg) {
        const uint8_t * ptr = (const uint8_t *) cfg;
        size_t sz = sizeof (struct config_t);
        size_t addr = 0x12340000; / * Dirección EEPROM * /
        dum (sz) {
        int res = eeprom_write (addr, * ptr);
        si (res resendi res;
        ptr ++;
        adición ++;
        sz--;
        }
        return sz;
        }

        • ian dice:

          nah, demasiado complejo. Demasiadas variables también.

          El primer problema es que solo puede escribir un byte a la vez. Así que escribe una rutina (podría hacer límites de eeprom marcando si es necesario, etc.), y suponiendo que no haga scripts de más de 254 bytes, probablemente no esté en un sistema limitado.
          Tan simple sería (y estoy de acuerdo en que esto podría optimizarse mucho más si la velocidad fuera un problema)

          bool write_eeprom_block (size_t addr, uint8_t datos[] , uint8_t de largo)
          {
          por (uint8_t yo = 0; yo[i]);
          devuelve verdadero;
          }

          (dado que necesita una verificación errónea, es mejor que devuelva verdadero / falso incluso en esta etapa).

          Entonces solo nombralo

          write_eeprom_block (dirección, (uint8_t *) & cfg, tamaño de (cfg);

          no se pueden encontrar sindicatos trabajando en la estructura de IU.

          • Moryc dice:

            Así que reemplazó union con una conversión de tipo explícita que hace exactamente lo mismo pero sin la palabra "union". Y con punteros que conviene evitar a toda costa. Además, esta es una gran aplicación y no se trasladará a otras plataformas sin una reescritura considerable ...

      • Redhatter (VK4MSL) dice:

        Así que reemplazó union con una conversión de tipo explícita que hace exactamente lo mismo pero sin la palabra "union". Y con punteros que conviene evitar a toda costa. Además, esta es una gran aplicación y no se trasladará a otras plataformas sin una reescritura considerable ...

        Sí ... usando un union no es más seguro en este caso. Además, realmente no veo cómo se puede escribir un programa sin sentido sin usar punteros. Tu punto de entrada suele tener el prototipo int main(int argc, char** argv);; piel, hay un puntero allí!

        A menos que viva declarando todo estáticamente en un lugar donde sea accesible globalmente (¡uh!), Tendrá sugerencias.

        • ian dice:

          sí, los punteros son mucho mejores (usados ​​correctamente) que la unión.
          la unión tiene efectos secundarios. Es así de simple. actualizas lo que parece una variable y otra variable cambia.
          También puede hacer esto con punteros, pero los programadores aceptables no significa que no tengan dos punteros diferentes al mismo objeto, a menos que haya algún código administrativo.
          E incluso entonces, ignorar un puntero claramente cambia algo en la memoria.

          A menos que tenga una razón muy específica para utilizar sindicatos, no creo que deba hacerlo.

          • Redhatter (VK4MSL) dice:

            Acerca de mi único caso de uso para uniones copie el tipo "Variante" en VisualBASIC; algo como:

            struct variant_t {
            union {
            void* ptr;
            uint64_t uint;
            int64_t int;
            double dbl;
            uint8_t byte[8];
            } value;
            uint32_t size;
            uint8_t type;
            uint8_t flags;
            };

            Esto permitiría una serie de tipos de datos C comunes, con struct un miembro que define cuál de estos union los miembros importan y cómo interpretarlo. te se type == TYPE_CHAR; entonces ptr debe ser considerado como char*. Si algo es terriblemente grande; podrías usar un poco en flags para indicar que size se mide en palabras de 16 bits o palabras largas de 32 bits. Si tienes una cuerda pequeña; lo llenarias byte y el type se arreglaría en consecuencia.

            Sí, la estructura tiene 16 bytes de longitud, pero te permitiría manejar casi cualquier cosa.

            Dicho esto, rara vez se necesita un animal así.

    • Chip dice:

      “Generalmente” puede ser cierto, ya que la gran mayoría de los desarrolladores no manejan “generalmente” los registros del dispositivo o tienen limitaciones de memoria que limitan la cantidad de almacenamiento que se puede usar. También tengo 40 años de experiencia, principalmente en manipulación de hardware (CPU, PCI, etc.). Su comentario sobre el código de mierda para usar uniones y estructuras es simplemente incorrecto. Sí, hay momentos y lugares apropiados para utilizar sindicatos y estructuras, pero distribuirlos a granel es falso; Utilizo estos edificios según sea necesario, no por capricho.

      Aquí hay un extracto de la segunda edición de [i]El lenguaje de programación C[/i] por Brian W. Kernighan y Dennis M. Ritchie

      http://www2.cs.uregina.ca/~hilder/cs430-833/Reference%20Materials/The%20C%20Programming%20Language.pdf

      6.9 Campos de bits

      Cuando se excede el espacio de almacenamiento, puede ser necesario empaquetar varios objetos en una palabra de máquina; un uso común es un conjunto de indicadores de un solo bit en aplicaciones como compilar matrices de símbolos. Los formatos de datos impuestos externamente, como las interfaces a los dispositivos de hardware, a menudo también requieren la capacidad de llegar a las palabras.

      Aquí está la definición del registro IA32_MC7_STATUS de Intel:

      typedef union
      {
          uint64_t Uint64;
          struct
          {
              uint64_t MCACOD          :16;    // bits 15:0
              uint64_t MscodDataRdErr  : 1;    // bit  16
              uint64_t RSVD_17_17      : 1;    // bit  17
              uint64_t MscodPtlWrErr   : 1;    // bit  18
              uint64_t MscodFullWrErr  : 1;    // bit  19
              uint64_t MscodBgfErr     : 1;    // bit  20
              uint64_t MscodTimeout    : 1;    // bit  21
              uint64_t MscodParErr     : 1;    // bit  22
              uint64_t MscodBucket1Err : 1;    // bit  23
              uint64_t MscodDdrType    : 2;    // bits 25:24
              uint64_t RSVD_31_26      : 6;    // bits 31:26
              uint64_t OTHER_INFO      : 6;    // bits 37:32
              uint64_t CORR_ERR_CNT    :15;    // bits 52:38
              uint64_t CORR_ERR_STATUS : 2;    // bits 54:53
              uint64_t AR              : 1;    // bit  55
              uint64_t S               : 1;    // bit  56
              uint64_t PCC             : 1;    // bit  57
              uint64_t ADDRV           : 1;    // bit  58
              uint64_t MISCV           : 1;    // bit  59
              uint64_t EN              : 1;    // bit  60
              uint64_t UC              : 1;    // bit  61
              uint64_t OVERFLOW        : 1;    // bit  62
              uint64_t VALID           : 1;    // bit  63
          } Bits;
      } IA32_MC7_STATUS;
      

      El uso de los campos anteriores es mucho más fácil de leer y mantener, y elimina las preocupaciones sobre qué bits se están manejando. Un antiguo colaborador desconocía los campos de bits en C, por lo que utilizó máscaras para acceder a los bits de los registros, a veces de forma incorrecta.

      • ian dice:

        Creo que te perdiste un poco lo que dije, ¡no creo que los tartamudeos fueran malos! ¡Ellos son buenos! ¡Úsalos en todas partes!

        En su ejemplo, veo por qué utilizó un sindicato. La función espera uint64_t, pero en realidad no lo es, porque es un campo de bits completo. Y usted (supongo) nunca cambiará manualmente Uint64, simplemente lo pasará a una función que no controla. Si la memoria es muy apretada, etc., puedo ver por qué haría eso.

        Sin embargo, sería mejor encapsularlo y transmitir una dirección de la estructura, sería mucho más claro y menos propenso a errores.

        Aunque mi punto de vista está significativamente sesgado como el mío
        1) escribe un código que algún idiota tendrá que conservar o cambiar en el futuro.
        2) a veces ese idiota soy yo porque todavía apoyo un código que escribí hace 30 años ...

        ¡Entonces las cosas que disminuyen la posibilidad de errores son buenas!

        • Moryc dice:

          Cada acceso al registro en un mundo de programación PIC usando XC8 usa la combinación de mapas de bits del registro en una estructura asociada con un nombre de registro. Entonces, por ejemplo, puedo acceder a un bit en el puerto usando PORTABits.RA0 o escribir en un puerto completo con PORTA. Entonces, si el compilador y el uso de IDE se unen todo el tiempo, ¿por qué es incorrecto usarlos en mi código?

          También para mejorar la legibilidad del código utilizo nombres significativos y muchos comentarios. Cualquiera que diga que el código debería ser su propia documentación es un tonto ...

      • Mate dice:

        Eso es exactamente lo que no es portátil.

  • Chris dice:

    Uso conexiones para código incrustado en microcontroladores con pequeñas cantidades de RAM. Declararé un conjunto global de uniones cuyo cada elemento puede ser una uint32_t, dos uint16_t variables o cuatro uint8_t variables (generalmente evito una coma en mi código incrustado). Cree las variables de bloque temporal adecuadas sin tener que declararlas dentro de cada función.

    • ian dice:

      No tendrá un código de deslizamiento si se preocupa por 8 bytes de RAM para variables que no se superponen. Debo admitir que solo recientemente inserté chips (el año pasado) alguna vez me sentí tentado a usar una diapositiva y, sinceramente, todavía no la he encontrado en ningún lugar que quisiera. ¡La biblioteca deslizante es demasiado grande!
      Entiendo lo que estás haciendo, pero lo que hago es tener una memoria que todas las funciones saben que pueden usar, pero no pueden confiar si están llamando a otra cosa o saliendo. tener que preocuparse por escribir accidentalmente en una variable mientras golpea otra (la forma de unión ...)

      • Megol dice:

        Un punto flotante es útil para algunos datos. Escribir código FP es trivial la mayor parte del tiempo (con flotantes que no son IEEE) y ocupa poco espacio.

        ¿Y cómo es su solución mejor que el sindicato? Simplemente use la unión sabiendo que los datos no son confiables si llaman a otra cosa -> no hay problema, alguna ventaja (código más agradable).

      • Chris dice:

        Todas las necesidades son un poco diferentes. Para mí, la principal ventaja de los números de punto flotante es su capacidad para contener números muy grandes o muy pequeños, pero no los necesito para la mayor parte de mi código. Prefiero usar (u) int32_t y precisión de punto fijo para hacer las matemáticas menos intensivas en computadora.

        En cuanto a la combinación, la uso para lanzar el "blob" de memoria que llamas el tipo de tipo de variable que necesito. Hace que el código sea muy limpio. Siempre queda claro de la inspección cómo uso la memoria y no tengo tipos de letra cargados en todas partes.

    • Julian Skidmore dice:

      "Cree variables de bloques de raspado convenientes sin tener que declararlas dentro de cada función"

      Entonces lo haces manualmente, ¿qué hace el compilador? En una CPU adecuada, una asignación de variable local de una función es siempre ideal, porque se asigna exactamente el espacio correcto de acuerdo con su necesidad, para una función y actualmente solo está ocupado el espacio requerido para las variables en vivo en todo el programa o hilo.

      En un PIC de 8 bits, el compilador intenta hacer esto creando una pila asignada estáticamente, pero las variables de diferentes funciones en el mismo nivel de llamada ocuparán las mismas direcciones RAM: son parte de una "unión". La única diferencia con hacerlo manualmente es que probablemente cometerá más errores.

  • msat dice:

    Soy C n00b y recientemente fundé sindicatos y este nombre. Varias cosas en un código que miré que no entendí y todavía no me molesté en mirar, y luego ¡BAM!: ¡HaD tiene una publicación sobre este tema a tiempo! Los comentarios también ayudaron a cosas a las que debería prestar atención si tengo la intención de escribir código que los use.

    • Redhatter (VK4MSL) dice:

      Sí, poner ese cliente secreto de escritorio remoto en su computadora ciertamente ayudó. :-PAG

      • Ken N dice:

        Supongo que el rastreador de mi computadora no funciona correctamente; Me vendría bien un poco de ayuda con las estructuras hace aproximadamente un mes.

        De todos modos, este fue un artículo bastante carnoso e interesante. Me asustó un poco de los sindicatos, aunque los conozco ahora, y en el futuro probablemente me encuentre con un problema en el que serán útiles.

        • Redhatter (VK4MSL) dice:

          Oye, no podemos mirar * todas * las computadoras; ¡Hay tanto monitor de espacio aquí!

    • Chris dice:

      Una gran ventaja de las estructuras es su uso para pasar datos entre funciones. A veces, especialmente cuando necesita actualizar alguna variable de estado, no sabe desde el principio todo lo que necesita incluir en esa variable de estado. ¡Estructuras (y uniones más pequeñas) al rescate! Simplemente transfiera una estructura o únase (o mejor un puntero a una, ya que las estructuras pasan copiando en la pila de manera diferente) a su función. Si encuentra que ha olvidado algo, simplemente agréguelo a su definición estructural y se transmitirá automáticamente para el viaje sin tener que modificar su definición funcional (es decir, la lista de parámetros permanece igual); simplemente accede a los nuevos miembros de la estructura.

  • Grapa dice:

    El único caso utilizable en el que (me gustaría) usar combinaciones es cuando una función es un remitente para otras, dependiendo de los datos de entrada, sin la necesidad de usar punteros en blanco y adivinar el tipo / formato de datos. Por ejemplo, señalización entre diferentes capas de programas.
    Cree una combinación de estructuras que tengan el mismo formato para algunas de las primeras variables (por ejemplo, SignalId, senderId, marca de tiempo) y el resto completamente diferente. Ahora el remitente puede verificar esos primeros valores (debido al mismo inicio de estructuras, siempre están en las mismas direcciones de memoria), lanzar el resto en la estructura correcta y llamar a la función requerida. El uso mínimo de memoria y el tipo de transmisión son siempre correctos ...

  • Jake Brodsky dice:

    La mayoría de ustedes escribe como si eso, la única y principal preocupación de TODA la programación en lenguaje C es escribir código portátil.

    Pero a veces no importa. Lo sé, eso suena a sacrilegio hoy en día. Pero hagámoslo realidad. A veces, necesita un código de interfaz desechable que hace algo muy específico en una plataforma muy estricta.

    Incluso cuando se escribió C por primera vez, era obvio que tendría que haber problemas específicos de la plataforma que debían reescribirse. La meta escribir un código estricto y de memoria eficiente seguía siendo muy problemático. Por eso existe la palabra clave union. Es por eso que C tiene todas esas formas asombrosas en las que puedes dispararte con punteros.

    Sí, al igual que las matemáticas de puntero, los sindicatos son herramientas peligrosas de usar y muy mal utilizadas. Pero a veces los necesitas.

    Si la velocidad y la eficiencia de la memoria son preocupaciones secundarias y la portabilidad es la principal, entonces use cualquier lenguaje avanzado que desee. El lenguaje de programación C probablemente no sea el lenguaje que debería utilizar.

    Y si REALMENTE no le importa la portabilidad, pero desea la mejor velocidad y eficiencia de memoria, el lenguaje macro ensamblador se convierte rápidamente en un arte perdido.

    • Fred dice:

      ¡Escucha Escucha!

      Escribo código para microcontroladores con mucha memoria limitada y, a veces, tengo que escribir un ensamblado para que coincida. Pero cuando no estoy haciendo eso, uso C, y la portabilidad es una de mis prioridades. El código estricto (y legible) está en la parte superior de mi lista. Y como usted dice, los consejos y las uniones son dos formas de ayudar con ese objetivo.

    • Chris dice:

      Esto ^

  • Rafael dice:

    Este código:

    union adc_data {
    estructura {
    uint8_t adcl;
    uint8_t adch;
    } valor uint16_t;
    };

    Falta un; allí.

    • Sven Gregori dice:

      ¡Esa es una especie de decisión!
      Pero sí, tienes razón, gracias por arreglarlo.

Ricardo Vicente
Ricardo Vicente

Deja una respuesta

Tu dirección de correo electrónico no será publicada.