Buscar..


Observaciones

La declaración de identificador que se refiere a un objeto o función a menudo se denomina abreviatura simplemente como una declaración de objeto o función.

Llamando a una función desde otro archivo C

foo.h

#ifndef FOO_DOT_H    /* This is an "include guard" */
#define FOO_DOT_H    /* prevents the file from being included twice. */
                     /* Including a header file twice causes all kinds */
                     /* of interesting problems.*/

/**
 * This is a function declaration.
 * It tells the compiler that the function exists somewhere.
 */
void foo(int id, char *name);

#endif /* FOO_DOT_H */

foo.c

#include "foo.h"    /* Always include the header file that declares something
                     * in the C file that defines it. This makes sure that the
                     * declaration and definition are always in-sync.  Put this
                     * header first in foo.c to ensure the header is self-contained.
                     */
#include <stdio.h>
                       
/**
 * This is the function definition.
 * It is the actual body of the function which was declared elsewhere.
 */
void foo(int id, char *name)
{
    fprintf(stderr, "foo(%d, \"%s\");\n", id, name);
    /* This will print how foo was called to stderr - standard error.
     * e.g., foo(42, "Hi!") will print `foo(42, "Hi!")`
     */
}

C Principal

#include "foo.h"

int main(void)
{
    foo(42, "bar");
    return 0;
}

Compilar y enlazar

En primer lugar, se compila tanto foo.c y main.c a los ficheros objeto. Aquí usamos el compilador gcc , su compilador puede tener un nombre diferente y necesitar otras opciones.

$ gcc -Wall -c foo.c
$ gcc -Wall -c main.c

Ahora los unimos para producir nuestro ejecutable final:

$ gcc -o testprogram foo.o main.o

Usando una variable global

El uso de variables globales es generalmente desaconsejado. Hace que su programa sea más difícil de entender y más difícil de depurar. Pero a veces usar una variable global es aceptable.

global.h

#ifndef GLOBAL_DOT_H    /* This is an "include guard" */
#define GLOBAL_DOT_H

/**
 * This tells the compiler that g_myglobal exists somewhere.
 * Without "extern", this would create a new variable named
 * g_myglobal in _every file_ that included it. Don't miss this!
 */
extern int g_myglobal; /* _Declare_ g_myglobal, that is promise it will be _defined_ by
                        * some module. */

#endif /* GLOBAL_DOT_H */

global.c

#include "global.h" /* Always include the header file that declares something
                     * in the C file that defines it. This makes sure that the
                     * declaration and definition are always in-sync.
                     */
                       
int g_myglobal;     /* _Define_ my_global. As living in global scope it gets initialised to 0 
                     * on program start-up. */

C Principal

#include "global.h"

int main(void)
{
    g_myglobal = 42;
    return 0;
}

Ver también ¿Cómo uso extern para compartir variables entre archivos de origen?

Uso de constantes globales

Los encabezados se pueden usar para declarar recursos de solo lectura de uso global, como tablas de cadenas, por ejemplo.

Declare a aquellos en un encabezado separado que se incluya en cualquier archivo (" Unidad de traducción ") que quiera usarlos. Es útil usar el mismo encabezado para declarar una enumeración relacionada para identificar todos los recursos de cadena:

recursos.h:

#ifndef RESOURCES_H
#define RESOURCES_H

typedef enum { /* Define a type describing the possible valid resource IDs. */
  RESOURCE_UNDEFINED = -1, /* To be used to initialise any EnumResourceID typed variable to be 
                              marked as "not in use", "not in list", "undefined", wtf.
                              Will say un-initialised on application level, not on language level. Initialised uninitialised, so to say ;-)
                              Its like NULL for pointers ;-)*/
  RESOURCE_UNKNOWN = 0,    /* To be used if the application uses some resource ID, 
                              for which we do not have a table entry defined, a fall back in 
                              case we _need_ to display something, but do not find anything 
                              appropriate. */

  /* The following identify the resources we have defined: */
  RESOURCE_OK,
  RESOURCE_CANCEL,
  RESOURCE_ABORT,
  /* Insert more here. */

  RESOURCE_MAX /* The maximum number of resources defined. */
} EnumResourceID;


extern const char * const resources[RESOURCE_MAX]; /* Declare, promise to anybody who includes 
                                      this, that at linkage-time this symbol will be around. 
                                      The 1st const guarantees the strings will not change, 
                                      the 2nd const guarantees the string-table entries 
                                      will never suddenly point somewhere else as set during 
                                      initialisation. */
#endif

Para definir realmente los recursos, se creó un archivo .c relacionado, que es otra unidad de traducción que contiene las instancias reales de lo que se había declarado en el archivo de encabezado relacionado (.h):

recursos.c:

#include "resources.h" /* To make sure clashes between declaration and definition are
                          recognised by the compiler include the declaring header into
                          the implementing, defining translation unit (.c file).

/* Define the resources. Keep the promise made in resources.h. */
const char * const resources[RESOURCE_MAX] = {
  "<unknown>",
  "OK",
  "Cancel",
  "Abort"
};

Un programa que usa esto podría verse así:

C Principal:

#include <stdlib.h> /* for EXIT_SUCCESS */
#include <stdio.h>

#include "resources.h"


int main(void)
{
  EnumResourceID resource_id = RESOURCE_UNDEFINED;

  while ((++resource_id) < RESOURCE_MAX)
  {
    printf("resource ID: %d, resource: '%s'\n", resource_id, resources[resource_id]);
  }

  return EXIT_SUCCESS;
}

Compile los tres archivos anteriores utilizando GCC y vincúlelos para que se conviertan en el archivo main del programa, por ejemplo, utilizando esto:

gcc -Wall -Wextra -pedantic -Wconversion -g  main.c resources.c -o main

(use estos -Wall -Wextra -pedantic -Wconversion para hacer que el compilador sea realmente delicado, para que no se pierda nada antes de publicar el código en SO, dirá que el mundo o incluso vale la pena implementarlo en producción)

Ejecuta el programa creado:

$ ./main

Y obten:

resource ID: 0, resource: '<unknown>'
resource ID: 1, resource: 'OK'
resource ID: 2, resource: 'Cancel'
resource ID: 3, resource: 'Abort'

Introducción

Ejemplos de declaraciones son:

int a; /* declaring single identifier of type int */

La declaración anterior declara un identificador único llamado a que se refiere a algún objeto con tipo int .

int a1, b1; /* declaring 2 identifiers of type int */

La segunda declaración declara 2 identificadores llamados a1 y b1 que se refieren a algunos otros objetos aunque con el mismo tipo int .

Básicamente, la forma en que funciona es así: primero se escribe un tipo y luego se escriben una o varias expresiones separadas mediante comas ( , ) ( que no se evaluarán en este punto) y que, de lo contrario, deberían denominarse declaradores en este contexto ). Al escribir tales expresiones, se le permite aplicar solo los operadores de indirección ( * ), llamada de función ( ( ) ) o subíndice (o indexación de matriz - [ ] ) en algún identificador (tampoco puede usar ningún operador). No es necesario que el identificador utilizado esté visible en el alcance actual. Algunos ejemplos:

/* 1 */ int /* 2 */ (*z) /* 3 */ , /* 4 */ *x , /* 5 */ **c /* 6 */ ;
# Descripción
1 El nombre del tipo entero.
2 Expresión no evaluada aplicando indirección a algún identificador z .
3 Tenemos una coma que indica que una expresión más seguirá en la misma declaración.
4 Expresión no evaluada aplicando indirección a algún otro identificador x .
5 Expresión sin evaluar aplicando indirección al valor de la expresión (*c) .
6 Fin de la declaración.

Tenga en cuenta que ninguno de los identificadores anteriores era visible antes de esta declaración y, por lo tanto, las expresiones utilizadas no serían válidas antes.

Después de cada expresión, el identificador utilizado en ella se introduce en el alcance actual. (Si el identificador le ha asignado un enlace, también se puede volver a declarar con el mismo tipo de enlace para que ambos identificadores se refieran al mismo objeto o función)

Además, el signo del operador igual ( = ) se puede usar para la inicialización. Si una expresión no evaluada (declarador) va seguida de = dentro de la declaración, decimos que el identificador que se está introduciendo también se está inicializando. Después del signo = podemos poner una vez más alguna expresión, pero esta vez será evaluada y su valor se usará como inicial para el objeto declarado.

Ejemplos:

int l = 90; /* the same as: */

int l; l = 90; /* if it the declaration of l was in block scope */

int c = 2, b[c]; /* ok, equivalent to: */

int c = 2; int b[c];

Más adelante en su código, se le permite escribir la misma expresión exacta de la parte de declaración del identificador recién introducido, lo que le da un objeto del tipo especificado al principio de la declaración, asumiendo que ha asignado valores válidos a todos los accesos Objetos en el camino. Ejemplos:

void f()
{
    int b2; /* you should be able to write later in your code b2 
            which will directly refer to the integer object
            that b2 identifies */
    
    b2 = 2; /* assign a value to b2 */
    
    printf("%d", b2); /*ok - should print 2*/

    int *b3; /* you should be able to write later in your code *b3 */

    b3 = &b2; /* assign valid pointer value to b3 */

    printf("%d", *b3); /* ok - should print 2 */

    int **b4; /* you should be able to write later in your code **b4 */

    b4 = &b3;

    printf("%d", **b4); /* ok - should print 2 */

    void (*p)(); /* you should be able to write later in your code (*p)() */

    p = &f; /* assign a valid pointer value */

    (*p)(); /* ok - calls function f by retrieving the
            pointer value inside p -    p
            and dereferencing it -      *p
            resulting in a function
            which is then called -      (*p)() -

            it is not *p() because else first the () operator is 
            applied to p and then the resulting void object is
            dereferenced which is not what we want here */
}

La declaración de b3 especifica que potencialmente puede utilizar el valor b3 como un medio para acceder a algún objeto entero.

Por supuesto, para aplicar indirection ( * ) a b3 , también debe tener un valor adecuado almacenado en él (vea los punteros para más información). También debe primero almacenar algo de valor en un objeto antes de intentar recuperarlo (puede ver más sobre este problema aquí ). Hemos hecho todo esto en los ejemplos anteriores.

int a3(); /* you should be able to call a3 */

Este le dice al compilador que intentará llamar a3 . En este caso, a3 refiere a la función en lugar de un objeto. Una diferencia entre el objeto y la función es que las funciones siempre tendrán algún tipo de enlace. Ejemplos:

void f1()
{
    {
        int f2(); /* 1 refers to some function f2 */
    }
    
    {
        int f2(); /* refers to the exact same function f2 as (1) */
    }
}

En el ejemplo anterior, las 2 declaraciones se refieren a la misma función f2 , mientras que si declararan objetos, en este contexto (con 2 ámbitos de bloque diferentes), tendrían que ser 2 objetos distintos distintos.

int (*a3)(); /* you should be able to apply indirection to `a3` and then call it */

Ahora puede parecer que se está complicando, pero si conoce la prioridad de los operadores, tendrá 0 problemas para leer la declaración anterior. Los paréntesis son necesarios porque el operador * tiene menos precedencia que el ( ) uno.

En el caso de usar el operador de subíndice, la expresión resultante no sería realmente válida después de la declaración porque el índice utilizado en él (el valor dentro de [ y ] ) siempre será 1 por encima del valor máximo permitido para este objeto / función.

int a4[5]; /* here a4 shouldn't be accessed using the index 5 later on */

Pero debería ser accesible por todos los demás índices inferiores a 5. Ejemplos:

a4[0], a4[1]; a4[4];

a4[5] resultará en UB. Más información sobre matrices se puede encontrar aquí .

int (*a5)[5](); /* here a4 could be applied indirection
                indexed up to (but not including) 5
                and called */

Desafortunadamente para nosotros, aunque sintácticamente posible, la declaración de a5 está prohibida por la norma actual.

Typedef

Los typedefs son declaraciones que tienen la palabra clave typedef delante y antes del tipo. P.ej:

typedef int (*(*t0)())[5];

( Técnicamente, también puede poner el typedef después del tipo, como este int typedef (*(*t0)())[5]; pero esto no es recomendable )

Las declaraciones anteriores declaran un identificador para un nombre typedef. Puedes usarlo así después:

t0 pf;

Que tendrá el mismo efecto que la escritura:

int (*(*pf)())[5];

Como puede ver, el nombre typedef "guarda" la declaración como un tipo que se usará más adelante para otras declaraciones. De esta manera puedes guardar algunas pulsaciones de teclado. Además, como la declaración que usa typedef sigue siendo una declaración, no está limitado solo por el ejemplo anterior:

t0 (*pf1);

Es lo mismo que:

int (*(**pf1)())[5];

Uso de la regla derecha-izquierda o espiral para descifrar la declaración de C

La regla "derecha-izquierda" es una regla completamente regular para descifrar las declaraciones C También puede ser útil para crearlos.

Lee los símbolos a medida que los encuentres en la declaración ...

*   as "pointer to"          - always on the left side
[]  as "array of"            - always on the right side
()  as "function returning"  - always on the right side

Cómo aplicar la regla.

PASO 1

Encuentra el identificador. Este es su punto de partida. Entonces di a ti mismo, "identificador es". Has comenzado tu declaración.

PASO 2

Mira los símbolos a la derecha del identificador. Si, por ejemplo, encuentra () allí, entonces sabe que esta es la declaración de una función. Entonces tendrías que "el identificador es la función que regresa" . O si encontraras un [] allí, dirías "el identificador es una matriz de" . Continúa a la derecha hasta que te quedes sin símbolos O golpeas un paréntesis derecho ) . (Si golpeas un paréntesis izquierdo ( , ese es el comienzo de un símbolo () , incluso si hay cosas entre los paréntesis. Más sobre esto más abajo).

PASO 3

Mira los símbolos a la izquierda del identificador. Si no es uno de nuestros símbolos anteriores (por ejemplo, algo como "int"), solo dígalo. De lo contrario, tradúzcalo al inglés usando la tabla de arriba. Sigue yendo a la izquierda hasta que te quedes sin símbolos O golpeas un paréntesis izquierdo ( .

Ahora repita los pasos 2 y 3 hasta que haya formado su declaración.


Aquí hay unos ejemplos:

int *p[];

Primero, encuentra el identificador:

int *p[];
     ^

"p es"

Ahora, muévete a la derecha hasta que salga de los símbolos o del paréntesis derecho.

int *p[];
      ^^

"p es una matriz de"

Ya no puedes moverte a la derecha (sin símbolos), así que muévete a la izquierda y encuentra:

int *p[];
    ^

"p es matriz de puntero a"

Sigue yendo a la izquierda y encuentra:

int *p[];
^^^

"p es la matriz de puntero a int".

(o "p es una matriz donde cada elemento es de tipo puntero a int" )

Otro ejemplo:

int *(*func())();

Encuentra el identificador.

int *(*func())();
       ^^^^

"func es"

Mover a la derecha.

int *(*func())();
           ^^

"func es la función de retorno"

Ya no se puede mover a la derecha debido al paréntesis derecho, así que muévete a la izquierda.

int *(*func())();
      ^

"func es la función que devuelve el puntero a"

Ya no puedo moverme a la izquierda debido al paréntesis izquierdo, así que sigue derecho.

int *(*func())();
              ^^

"func es la función que devuelve el puntero a la función que regresa"

Ya no puedes moverte a la derecha porque nos hemos quedado sin símbolos, así que ve a la izquierda.

int *(*func())();
    ^

"func es la función que devuelve el puntero a la función que devuelve el puntero a"

Y finalmente, sigue hacia la izquierda, porque no queda nada a la derecha.

int *(*func())();
^^^

"func es la función que devuelve el puntero a la función que devuelve el puntero a int".

Como puedes ver, esta regla puede ser bastante útil. También puede usarlo para comprobar la cordura mientras crea las declaraciones y para darle una pista sobre dónde colocar el siguiente símbolo y si se requieren paréntesis.

Algunas declaraciones parecen mucho más complicadas que debido a los tamaños de matriz y las listas de argumentos en forma de prototipo. Si ve [3] , se lee como "matriz (tamaño 3) de ..." . Si ve (char *,int) , se lee como * "función esperando (char , int) y devolviendo ..." .

Aquí hay una divertida:

int (*(*fun_one)(char *,double))[9][20];

No voy a seguir cada uno de los pasos para descifrar este.

* "fun_one es un puntero a la función esperando (char , doble) y devuelve el puntero a la matriz (tamaño 9) de la matriz (tamaño 20) de int."

Como puede ver, no es tan complicado si se deshace de los tamaños de matriz y las listas de argumentos:

int (*(*fun_one)())[][];

Puede descifrarlo de esa manera, y luego colocarlo en la matriz de tamaños y listas de argumentos más adelante.

Algunas palabras finales:


Es muy posible hacer declaraciones ilegales usando esta regla, por lo que es necesario conocer algo de lo que es legal en C. Por ejemplo, si lo anterior hubiera sido:

int *((*fun_one)())[][];

habría leído "fun_one es un puntero a la función que devuelve una matriz de matriz de puntero a int" . Dado que una función no puede devolver una matriz, sino solo un puntero a una matriz, esa declaración es ilegal.

Las combinaciones ilegales incluyen:

[]() - cannot have an array of functions
()() - cannot have a function that returns a function
()[] - cannot have a function that returns an array

En todos los casos anteriores, necesitaría un conjunto de paréntesis para enlazar un símbolo * a la izquierda entre estos () y [] símbolos del lado derecho para que la declaración sea legal.

Aquí hay algunos ejemplos más:


Legal

int i;               an int
int *p;              an int pointer (ptr to an int)
int a[];             an array of ints
int f();             a function returning an int
int **pp;            a pointer to an int pointer (ptr to a ptr to an int)
int (*pa)[];         a pointer to an array of ints
int (*pf)();         a pointer to a function returning an int
int *ap[];           an array of int pointers (array of ptrs to ints)
int aa[][];          an array of arrays of ints
int *fp();           a function returning an int pointer
int ***ppp;          a pointer to a pointer to an int pointer
int (**ppa)[];       a pointer to a pointer to an array of ints
int (**ppf)();       a pointer to a pointer to a function returning an int
int *(*pap)[];       a pointer to an array of int pointers
int (*paa)[][];      a pointer to an array of arrays of ints
int *(*pfp)();       a pointer to a function returning an int pointer
int **app[];         an array of pointers to int pointers
int (*apa[])[];      an array of pointers to arrays of ints
int (*apf[])();      an array of pointers to functions returning an int
int *aap[][];        an array of arrays of int pointers
int aaa[][][];       an array of arrays of arrays of int
int **fpp();         a function returning a pointer to an int pointer
int (*fpa())[];      a function returning a pointer to an array of ints
int (*fpf())();      a function returning a pointer to a function returning an int

Ilegal

int af[]();          an array of functions returning an int
int fa()[];          a function returning an array of ints
int ff()();          a function returning a function returning an int
int (*pfa)()[];      a pointer to a function returning an array of ints
int aaf[][]();       an array of arrays of functions returning an int
int (*paf)[]();      a pointer to a an array of functions returning an int
int (*pff)()();      a pointer to a function returning a function returning an int
int *afp[]();        an array of functions returning int pointers
int afa[]()[];       an array of functions returning an array of ints
int aff[]()();       an array of functions returning functions returning an int
int *fap()[];        a function returning an array of int pointers
int faa()[][];       a function returning an array of arrays of ints
int faf()[]();       a function returning an array of functions returning an int
int *ffp()();        a function returning a function returning an int pointer

Fuente: http://ieng9.ucsd.edu/~cs30x/rt_lt.rule.html



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow