Solución para & # 8220; desreferenciar el puntero 'void *' & # 8221; advertencia en struct en C?

StackOverflow https://stackoverflow.com/questions/289329

  •  08-07-2019
  •  | 
  •  

Pregunta

Estaba tratando de crear una pseudo superestructura para imprimir una matriz de estructuras. Mi basico las estructuras son las siguientes.

/* Type 10 Count */
typedef struct _T10CNT
{
    int _cnt[20];
} T10CNT;

...

/* Type 20 Count */
typedef struct _T20CNT
{
    long _cnt[20];
} T20CNT;
...

Creé la siguiente estructura para imprimir la matriz de estructuras mencionadas anteriormente. Obtuve un error de anulación del puntero vacío al compilar el fragmento de código siguiente.

typedef struct _CMNCNT
{
    long  _cnt[3];
} CMNCNT;

static int printCommonStatistics(void *cmncntin, int cmncnt_nelem, int cmncnt_elmsize)
{
    int ii;
    for(ii=0; ii<cmncnt_nelem; ii++)
    {
        CMNCNT *cmncnt = (CMNCNT *)&cmncntin[ii*cmncnt_elmsize];
        fprintf(stout,"STATISTICS_INP: %d\n",cmncnt->_cnt[0]);
        fprintf(stout,"STATISTICS_OUT: %d\n",cmncnt->_cnt[1]); 
        fprintf(stout,"STATISTICS_ERR: %d\n",cmncnt->_cnt[2]);
    }
    return SUCCESS;
}

T10CNT struct_array[10];
...
printCommonStatistics(struct_array, NELEM(struct_array), sizeof(struct_array[0]);
...

Mi intención es tener una función común para imprimir todas las matrices. Avíseme la forma correcta de usarlo.

Agradezco la ayuda por adelantado.

Editar: el nombre del parámetro se cambia a cmncntin de cmncnt. Lo sentimos, fue un error tipográfico.

Gracias Mathew Liju

¿Fue útil?

Solución

Creo que su diseño va a fallar, pero tampoco estoy convencido de que las otras respuestas que veo aborden por completo las razones más profundas.

Parece que está tratando de usar C para tratar con tipos genéricos, algo que siempre se vuelve peludo. Puede hacerlo, si tiene cuidado, pero no es fácil, y en este caso, dudo que valga la pena.

Razón más profunda : supongamos que superamos los simples problemas sintácticos (o apenas más que sintácticos). Su código muestra que T10CNT contiene 20 int y T20CNT contiene 20 long . En máquinas modernas de 64 bits, excepto bajo Win64: sizeof (long)! = Sizeof (int) . Por lo tanto, el código dentro de su función de impresión debe distinguir entre desreferenciar las matrices int y las matrices long . En C ++, hay una regla por la que no debes tratar las matrices polimórficamente, y este tipo de cosas es la razón. El tipo CMNCNT contiene 3 valores long ; diferentes en número de las estructuras T10CNT y T20CNT, aunque el tipo base de la matriz coincide con T20CNT.

Recomendación de estilo : recomiendo encarecidamente evitar los guiones bajos en los nombres. En general, los nombres que comienzan con guiones bajos están reservados para que la implementación los use y los use como macros. Las macros no respetan el alcance; si la implementación define una macro _cnt, destruiría su código. Hay matices en qué nombres están reservados; No voy a entrar en esos matices. Es mucho más simple pensar que 'los nombres que comienzan con guiones bajos están reservados', y te evitará problemas.

Sugerencia de estilo : su función de impresión devuelve el éxito incondicionalmente. Eso no es sensato; su función no debe devolver nada, para que la persona que llama no tenga que probar el éxito o el fracaso (ya que nunca puede fallar). Un codificador cuidadoso que observe que la función devuelve un estado siempre probará el estado de devolución y tendrá un código de manejo de errores. Ese código nunca se ejecutará, por lo que está muerto, pero es difícil para cualquiera (o el compilador) determinar eso.

Surface Fix : Temporalmente, podemos suponer que puede tratar int y long como sinónimos; Pero debes dejar el hábito de pensar que son sinónimos. El argumento void * es la forma correcta de decir "esta función toma un puntero de tipo indeterminado". Sin embargo, dentro de la función, debe convertir un void * a un tipo específico antes de realizar la indexación.

typedef struct _CMNCNT
{
    long    count[3];
} CMNCNT;

static void printCommonStatistics(const void *data, size_t nelem, size_t elemsize)
{
    int i;
    for (i = 0; i < nelem; i++)
    {
        const CMNCNT *cmncnt = (const CMNCNT *)((const char *)data + (i * elemsize));
        fprintf(stdout,"STATISTICS_INP: %ld\n", cmncnt->count[0]);
        fprintf(stdout,"STATISTICS_OUT: %ld\n", cmncnt->count[1]); 
        fprintf(stdout,"STATISTICS_ERR: %ld\n", cmncnt->count[2]);
    }
}

(También me gusta la idea de una secuencia de archivos llamada stout . Sugerencia : use cut'n'paste en el código fuente real, ¡es más seguro! m generalmente usa " sed 's / ^ / /' file.c " para preparar el código para cortar y pegar en una respuesta SO.)

¿Qué hace esa línea de lanzamiento? Me alegra que hayas preguntado ...

  • La primera operación es convertir el const void * en un const char * ; esto le permite realizar operaciones de tamaño de byte en la dirección. En los días anteriores al Estándar C, se utilizó char * en lugar de void * como mecanismo de direccionamiento universal.
  • La siguiente operación agrega el número correcto de bytes para llegar al inicio del elemento i th de la matriz de objetos de tamaño elemsize .
  • El segundo elenco luego le dice al compilador "confía en mí, sé lo que estoy haciendo". y "tratar esta dirección como la dirección de una estructura CMNCNT".

A partir de ahí, el código es bastante fácil. Tenga en cuenta que, dado que la estructura CMNCNT contiene el valor long , utilicé % ld para decir la verdad a fprintf () .

Dado que no está a punto de modificar los datos en esta función, no es una mala idea usar el calificador const como lo hice yo.

Tenga en cuenta que si va a

Otros consejos

El tipo de vacío se deja deliberadamente incompleto. De esto, se deduce que no puede desreferenciar los punteros nulos, y tampoco puede tomar el tamaño de la misma. Esto significa que no puede usar el operador de subíndice usándolo como una matriz.

En el momento en que asigna algo a un puntero vacío, cualquier información de tipo del original apuntado a tipo se pierde, por lo que solo puede desreferenciar si primero lo devuelve al tipo de puntero original.

Primero y lo más importante, pasa T10CNT * a la función, pero intenta escribirlo (y desreferenciarlo) a CMNCNT * en su función. Este no es un comportamiento válido e indefinido.

Necesita una función printCommonStatistics para cada tipo de elementos de matriz. Entonces, ten un printCommonStatisticsInt , printCommonStatisticsLong , printCommonStatisticsChar , que difieren en su primer argumento (uno toma int * , el otro toma < code> long * , y así sucesivamente). Puede crearlos usando macros, para evitar código redundante.

Pasar la estructura en sí no es una buena idea, ya que entonces debe definir una nueva función para cada tamaño diferente de la matriz contenida dentro de la estructura (ya que son todos tipos diferentes). Así que mejor pase directamente la matriz contenida ( struct_array [0] ._ cnt , llame a la función para cada índice)

Cambie la declaración de función a char * así:

static int printCommonStatistics(char *cmncnt, int cmncnt_nelem, int cmncnt_elmsize)

el tipo vacío no asume ningún tamaño en particular, mientras que un carácter asumirá un tamaño de byte.

No puedes hacer esto:

cmncnt->_cnt[0]

si cmnct es un puntero nulo.

Debe especificar el tipo. Es posible que deba repensar su implementación.

La función

static int printCommonStatistics(void *cmncntin, int cmncnt_nelem, int cmncnt_elmsize)
{
    char *cmncntinBytes;
    int ii;

    cmncntinBytes = (char *) cmncntin;
    for(ii=0; ii<cmncnt_nelem; ii++)
    {
        CMNCNT *cmncnt = (CMNCNT *)(cmncntinBytes + ii*cmncnt_elmsize);  /* Ptr Line */
        fprintf(stdout,"STATISTICS_INP: %d\n",cmncnt->_cnt[0]);
        fprintf(stdout,"STATISTICS_OUT: %d\n",cmncnt->_cnt[1]); 
        fprintf(stdout,"STATISTICS_ERR: %d\n",cmncnt->_cnt[2]);
    }
    return SUCCESS;
}

Funciona para mí.

El problema es que en la línea comentó "Ptr Line" el código agrega un puntero a un entero. Dado que nuestro puntero es un char *, avanzamos en la memoria sizeof (char) * ii * cmncnt_elemsize, que es lo que queremos ya que un char es un byte. Su código intentó hacer una cosa equivalente avanzando sizeof (void) * ii * cmncnt_elemsize, pero void no tiene un tamaño, por lo que el compilador le dio el error.

Cambiaría T10CNT y T20CNT para usar ambos int o long en lugar de uno con cada uno. Estás dependiendo de sizeof (int) == sizeof (long)

En esta línea:

CMNCNT *cmncnt = (CMNCNT *)&cmncnt[ii*cmncnt_elmsize];

Está intentando declarar una nueva variable llamada cmncnt, pero ya existe una variable con este nombre como parámetro de la función. Es posible que desee utilizar un nombre de variable diferente para resolver esto.

También es posible que desee pasar un puntero a un CMNCNT a la función en lugar de un puntero vacío, porque entonces el compilador hará la aritmética del puntero por usted y no tiene que lanzarlo. No veo el punto de pasar un puntero vacío cuando todo lo que haces con él es lanzarlo a un CMNCNT. (Por cierto, que no es un nombre muy descriptivo para un tipo de datos).

Tu expresión

(CMNCNT *)&cmncntin[ii*cmncnt_elmsize]

intenta tomar la dirección de cmncntin [ii * cmncnt_elmsize] y luego convierte ese puntero para escribir (CMNCNT *). No puede obtener la dirección de cmncntin [ii * cmncnt_elmsize] porque cmncntin tiene el tipo void *.

Estudie las precedencia de operadores de C e inserte paréntesis cuando sea necesario.

Punto de información: el relleno interno realmente puede arruinar esto.

Considere struct {char c [6]; }; - Tiene sizeof () = 6. Pero si tuviera una matriz de estos, ¡cada elemento podría rellenarse con una alineación de 8 bytes!

Ciertas operaciones de ensamblaje no manejan datos mal alineados correctamente. (Por ejemplo, si un int abarca dos palabras de memoria.) (SÍ, he sido mordido por esto antes).

.

Segundo: en el pasado, he usado matrices de tamaño variable. (Era tonto en ese entonces ...) Funciona si no está cambiando el tipo. (O si tiene una unión de los tipos).

Por ejemplo:

struct T { int sizeOfArray;  int data[1]; };

Asignado como

T * t = (T *) malloc( sizeof(T) + sizeof(int)*(NUMBER-1) );
                      t->sizeOfArray = NUMBER;

(Aunque el relleno / alineación aún puede arruinarte).

.

Tercero: Considere:

   struct T {
     int sizeOfArray;
     enum FOO arrayType;
     union U { short s; int i; long l; float f; double d; } data [1];
    };

Resuelve problemas al saber cómo imprimir los datos.

.

Cuarto: puedes pasar la matriz int / long a tu función en lugar de a la estructura. Por ejemplo:

void printCommonStatistics( int * data, int count )
{
  for( int i=0;  i<count;  i++ )
    cout << "FOO: " << data[i] << endl;
}

Se invoca a través de:

_T10CNT  foo;
printCommonStatistics( foo._cnt, 20 );

O:

 int a[10], b[20], c[30];
printCommonStatistics( a, 10 );
printCommonStatistics( b, 20 );
printCommonStatistics( c, 30 );

Esto funciona mucho mejor que ocultar datos en estructuras. A medida que agrega miembros a una de sus estructuras, el diseño puede cambiar entre las de su estructura y dejar de ser coherente. (Es decir, la dirección de _cnt en relación con el inicio de la estructura puede cambiar para _T10CNT y no para _T20CNT. Diversos tiempos de depuración allí. Una sola estructura con una carga útil _cnt unida evitaría esto).

Por ejemplo:

struct FOO {
  union {
         int     bar  [10];
          long biff [20];
   } u;
}

.

Quinto: Si debe utilizar estructuras ... C ++, iostreams y plantillas sería mucho más limpio de implementar.

Por ejemplo:

template<class TYPE> void printCommonStatistics( TYPE & mystruct, int count )
{
  for( int i=0;  i<count;  i++ )
    cout << "FOO: " << mystruct._cnt[i] << endl;
}      /* Assumes all mystruct's have a "_cnt" member. */

Pero eso probablemente no sea lo que estás buscando ...

C no es mi taza o'java, pero creo que su problema es que "void * cmncnt" debe ser CMNCNT * cmncnt.

Siéntanse libres de corregirme ahora, programadores en C, y díganme que es por eso que los programadores de Java no pueden tener cosas buenas.

Esta línea está un poco torturada, ¿no te parece?

CMNCNT *cmncnt = (CMNCNT *)&cmncntin[ii*cmncnt_elmsize];

¿Qué tal algo más como

CMNCNT *cmncnt = ((CMNCNT *)(cmncntin + (ii * cmncnt_elmsize));

O mejor aún, si cmncnt_elmsize = sizeof (CMNCNT)

CMNCNT *cmncnt = ((CMNCNT *)cmncntin) + ii;

Eso también debería eliminar la advertencia, ya que ya no está desreferenciando un vacío *.

Por cierto: no estoy muy seguro de por qué lo está haciendo de esta manera, pero si cmncnt_elmsize a veces no es sizeof (CMNCNT), y de hecho puede variar de una llamada a otra, sugeriría repensar este diseño. Supongo que podría haber una buena razón para ello, pero me parece realmente inestable. Casi puedo garantizar que hay una mejor manera de diseñar cosas.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top