Ejecución directa de memorias: punteros funcionales en C

En la primera parte de esta serie, tratamos los conceptos básicos de los punteros en C, y pasamos a diseños más complejos y aritmética de punteros en la segunda parte. En ambas ocasiones nos centramos solo en punteros que representan datos en la memoria.

Pero los datos no son lo único que hay en la memoria. Todo el código del programa es accesible a través de RAM u otro tipo de memoria ejecutable, dando a cada función una dirección específica dentro de esa memoria como punto de entrada. Nuevamente, los punteros son simplemente direcciones de memoria y, para aprovechar al máximo esta similitud, C proporciona el concepto de punteros funcionales. Los punteros funcionales nos brindan formas de acelerar la ejecución de código condicional, implementar devoluciones de llamadas para hacer que el código sea más modular e incluso dar un punto de apoyo al código de máquina actual para la ingeniería inversa o la explotación. ¡Así que sigue leyendo!

Punteros funcionales

En general, los apuntadores a funciones no son más misteriosos que los apuntadores a datos: la principal diferencia es que uno hace referencia a variables y el otro hace referencia a funciones. Si te acuerdas de la última vez como tablas decaer en punteros a su primer elemento, una función igualmente expira en un puntero a la dirección de su punto de entrada, con el () operador realizando todo lo que se encuentra en esa dirección. Como resultado, podemos declarar una variable de puntero funcional fptr y asignar una función func() lo: fptr = func;. Vocación fptr(); luego resuelva al punto de entrada de una función func() y ejecutarlo.

Es cierto que la idea de transformar una función en una variable puede parecer extraña al principio y puede requerir cierta personalización, pero se vuelve más fácil con el tiempo y puede ser un lenguaje muy útil. Lo mismo ocurre con la sintaxis de una función de visualización, que puede ser aterradora y confusa al principio. Pero miremos eso nosotros mismos.

Sintaxis de puntero funcional

Si rompemos una declaración de función estándar, tenemos tipo de retorno, a nombrey una lista opcional de parámetros: returntype name(parameters). Defina un puntero funcional análogo. El puntero de función debe tener un tipo de retorno y parámetros que coincidan con la función a la que hace referencia. Y al igual que con los punteros de datos, usamos el asterisco * declarar el puntero.

Hay una trampa. Si tenemos una función int func(void), escribiendo int *fptr(void) no nos dará un puntero a función, sino que otra función devuelve un puntero entero int * como el * se adhiere al int por defecto. Los corchetes harán que la declaración sea una declaración de puntero funcional agrupando el asterisco con el nombre del puntero en lugar del tipo de retorno: int (*fptr)(void).

Entonces el patrón general es: returntype (*name)(parameters). Echemos un vistazo a algunos indicadores de función diferentes.

// function without parameters returning int, the one we just had
int (*fptr1)(void); // --> int function1(void) { ... }

// function with one int parameter returning int
int (*fptr2)(int);  // --> int function2(int param) { ... }

// function with void * parameters returning char *
char *(*fptr3)(void *); // --> char *function3(void *param) { ... }

// function with fptr1 type function pointer as parameter returning void
void (*fptr4)(int (*)(void)); // --> void function4(int (*param)(void)) { ... }

// function with int parameter returning pointer to a fptr1 type function
int (*(*fptr5)(int))(void); // --> int (*function5(int))(void);

Obviamente, la sintaxis puede complicarse bastante y es posible que pierda el tiempo tratando de averiguarlo. Si alguna vez encuentra un puntero en un estado natural y tiene dificultades para averiguar qué va a dónde, el cdecl una herramienta de línea de comandos puede ser de gran ayuda y también viene como una versión en línea.

cdecl> explain int (*fptr1)(void)
declare fptr1 as pointer to function (void) returning int
cdecl> explain int (*(*fptr5)(int))(void)
declare fptr5 as pointer to function (int) returning pointer to function (void) returning int
cdecl>

Por otro lado, si se encuentra en una situación en la que necesita escribir un puntero que podría hacer varios intentos para ajustarlo, probablemente sea una buena idea usar Cs. typedef operador.

// define new type "mytype" as int
typedef mytype int;
mytype x = 123;

// define new type "fptr_t" as pointer to a function without parameters returning int
typedef int (*fptr_t)(void);
fptr_t fptr = function1;
fptr();

// use fptr_t as parameter and return type
void function4(fptr_t param) { ... }
fptr_t function5(void);

Es posible que haya notado que todos los ejemplos ni siquiera usaban el signo & cuando funciones de referencia, ni el asterisco * al desreferenciar punteros para realizar sus funciones subyacentes. Debido a que las funciones están predeterminadas a los punteros, no hay necesidad de usarlas, pero podemos hacerlo. Tenga en cuenta que una llamada a función tiene una prioridad más alta que una no referencia, por lo que debemos usar paréntesis en consecuencia.

// explicitly referencing with ampersand
fptr_t fptr = &function1;
// explicitly deferencing with asterisk
(*fptr)();
// not *fptr();

Si utilizar el signo o un asterisco es, en última instancia, una cuestión de gustos. Si tiene derecho a elegir por sí mismo, elija uno que le resulte más natural y más fácil de leer y comprender. Para nuestros ejemplos aquí, omitiremos ambos en el código, pero agregaremos la notación alternativa como comentarios.

Asignar y utilizar punteros de función

Ahora que hemos visto cómo declarar punteros de función, es hora de usarlos. En el verdadero espíritu de los punteros, nuestro primer ejemplo de estudio de caso es la referencia a un artículo anterior que trata sobre la implementación de una máquina de estados con punteros funcionales. Vale la pena leerlo por su cuenta, especialmente si desea saber más sobre máquinas de última generación, por lo que no queremos revelar demasiado aquí. Pero resumido, en lugar de usar un switch declaración para determinar y abordar el estado actual, las funciones operativas de estado asignan el siguiente administrador de estado a una variable de puntero de función. El programa en sí realiza periódicamente cualquier función almacenada en la variable.

void (*handler)(void);

void handle_some_state(void) {
    if (condition) {
        handler = handle_some_other_state;
     // handler = &handle_some_other_state;
    }
}

int main(void) {
    while (1) {
        handler();
    // (*handler)();
        ...
    }
    return 0;
}

Conjuntos de punteros funcionales

Podemos aplicar un enfoque similar cuando no tenemos ninguna transición de estado predefinida o lineal, pero, por ejemplo, queremos tratar con la entrada de usuario aleatoria, con cada entrada nombrando su propia función.

void handle_input(int input) {
    switch (input) {
        case 0:
            do_something();
            break;
        case 1:
            do_something_else();
            break;
        ...
    }
}

// function that is periodically called from main()
void handler_loop(void) {
    status = read_status_from_somewhere();
    handle_input(status);
}

Debido a que los punteros de función son solo variables, podemos insertarlos en una matriz. Al igual que con otras matrices, los paréntesis [] están vinculados directamente al nombre.

// declare array of function pointer of type void func(void)
void(*function_array[])(void) = {
    do_something,      // &do_something,
    do_something_else, // &do_something_else,
    ...
};

Ahora podemos reemplazar el anterior. switch reclamar por uso input como índice de tabla:

void handle_input(int input) {
    // execute whichever function is at array index "input"
    function_array[input]();
 // (*function_array[input])();
}

En teoría, esto cambió la complejidad de O(n) al O(1) e hizo que el tiempo de ejecución fuera más predecible, pero en la práctica hay muchos otros factores, como la arquitectura, la predicción de ramas y la optimización del compilador. En un microcontrolador simple, los indicadores funcionales suelen ser más rápidos que if/then o case reclamación (es. Sin embargo, los indicadores funcionales no son gratuitos. Cada puntero necesita un lugar en la memoria, y desreferenciar el puntero requiere copiar la dirección, agregando algunos bucles de CPU adicionales.

Un argumento más convincente para reemplazar el switch reclamar con punteros de función es la flexibilidad añadida. Ahora podemos configurar y reemplazar la función de manejo durante el tiempo de ejecución, lo cual es particularmente útil cuando escribimos una biblioteca o algún marco complementario donde proporcionamos la lógica principal, y dejamos que el usuario decida qué hacer realmente en cada estado / entrada / evento. / etc. (Ingrese aquí un comentario sobre un usuario de entrada de salud). Naturalmente, usar una matriz tiene más sentido si los estados o valores de entrada con los que estamos tratando son números enteros en orden secuencial. En otros casos, es posible que necesitemos otra solución.

Punteros funcionales como parámetros funcionales

Supongamos que hemos creado una biblioteca que envía solicitudes HTTP a un servidor web y los usuarios definidos por el usuario manejan su respuesta según el código HTTP. Tomar el código de estado como un índice de matriz nos dará una matriz enorme, en su mayoría vacía, desperdiciando mucha memoria. El viejo switch reclamo sería la mejor opción aquí. Bueno, en lugar de ajustar el cuerpo del handle_input() función, ¿cómo convertimos todo en un puntero de función? Acabas de inventar el callback.

// function pointer to the handle function
void (*handle_input_callback)(int);

// function to set our own handler
void set_input_handler(void (*callback)(int)) {
    handle_input_callback = callback;
}

// same handler, just using the function pointer now
void handler_loop(void) {
    status = read_status_from_somewhere();
    if (handle_input_callback != NULL) {
        // execute only if a handler was set
        handle_input_callback(status);
     // (*handle_input_callback)(status);
    }
}

En el lado definido por el uso del código, declararíamos nuestra propia función operativa y la pasaríamos.

void my_input_handler(int status) {
    switch (status) {
        case 200: // OK
            ...
            break;
        case 404: // Not Found
            ...
    }
}

int main(void) {
    set_input_handler(my_input_handler);
 // set_input_handler(&my_input_handler);
    while (1) {
        handler_loop();
        ...
    }
}

Un sistema del mundo real que utiliza en gran medida las revocaciones para el comportamiento definido por el usuario es el SDK que no es de OS C para ESP8266. Un ejemplo en el que se utiliza un parámetro de puntero de función para definir el comportamiento adecuado de la función se puede encontrar en la función Clasificación rápida de la biblioteca estándar C, qsort(), que toma una función de comparación como parámetro para determinar el orden de algún tipo de datos dado.

Punteros funcionales como struct Miembros

Después de todo lo que hemos visto ahora sobre punteros, no se sorprenda de que los punteros funcionales puedan ser struct miembros, y los declaramos y usamos de la misma manera que cualquier otro miembro.

struct something {
    int regular_member;
    void (*some_handler)(void);
    int (*another_handler)(char *, int);
} foo;

...
    foo.some_handler = some_function;
 // foo.some_handler = &some_function;
    foo.another_handler(buf, size);
 // (*foo.another_handler)(buf, size);
...

structs con una variedad de punteros funcionales, ya que los miembros se encuentran a menudo en sistemas enchufables, o donde el código dependiente del hardware está separado de la lógica común independiente del hardware. Un ejemplo clásico son los controladores de Linux, pero no tiene por qué ser tan extremo para los principiantes. ¿Recuerda ese ejemplo de alternancia de LED demasiado complejo de la primera parte donde definimos el puerto GPIO como un puntero? Si tuviéramos que usar el mismo concepto para la entrada de botones y agregar algunas funciones operativas, terminaríamos con un marco operativo de botones completo, general e independiente del hardware.

Tenga en cuenta que podemos asignar un NULL puntero también a punteros de función, y realizar dicha función resultará en lo mismo que ignorar cualquier otro NULL puntero en falla de segmentación. Pero si lo esperamos como un valor posible y lo comparamos antes de la ejecución, podemos usarlo para deshabilitar la ejecución y hacer que el manejo en sí sea opcional.

Casting de punteros funcionales

Si los punteros funcionales no se comportan de manera diferente a cualquier otro puntero, deberíamos poder lanzarlos a otros punteros a voluntad. Y sí, es técnicamente posible, y el compilador no nos detendrá, pero desde el punto de vista del lenguaje estándar, las posibilidades de ejecutar un puntero de función de rol de este tipo darán como resultado un comportamiento indefinido. Tome el siguiente ejemplo:

int function(int a, int b) { ... }

void foo(void) {
    // cast function to type "int func(int)"
    int (*fptr)(int) = (int (*)(int)) function;
    fptr(10); // -> function(10, ???);
}

Aunque es un elenco técnicamente válido, terminamos llamando function() sin valor por parámetro b. Cómo se maneja esto depende del sistema subyacente y su convención de llamada. A menos que tenga buenas razones, probablemente no desee lanzar una función a un indicador de función que no coincida. La curiosidad, sin embargo, es siempre una buena razón.

Conversión entre punteros funcionales y punteros de datos

¿Puede transformar funciones en datos o datos en funciones? ¡Por supuesto! Y hacerlo es una de las piedras angulares de la piratería. Como recordamos, necesitamos una dirección accesible y suficiente memoria asignada en esa dirección para procesar correctamente los punteros de datos. Con una función tenemos ambos: el código de función como memoria asignada y la función en sí como dirección. Si lanzamos un puntero de función a, digamos, un unsigned char puntero, desreferenciar el puntero nos dará el código de máquina compilado para esa función, listo para ingeniería inversa.

Haciendo lo contrario, cualquier puntero de datos que arrojemos a un puntero funcional se puede ejecutar si apunta a un código de máquina válido. Reunir un código de máquina en sí mismo, ocultarlo en una variable, remodelar un puntero a esa variable y finalmente operarlo, ciertamente parece una gran molestia para el uso diario. Pero esa es la receta básica para explotar las vulnerabilidades de seguridad mediante la inyección de código shell, y una forma generalmente divertida de experimentar con código de máquina, y puede que valga la pena su propio artículo en el futuro.

Multa

Si bien los indicadores pueden crear dolores de cabeza y frustración, también nos ofrecen libertad y posibles formas de trabajar que rara vez se igualan con otras construcciones lingüísticas. Y aunque el mundo puede ser un lugar más seguro gracias a los proyectos de lenguaje moderno que “resuelven” los problemas que pueden surgir con los punteros si no se manejan con cuidado, los punteros siempre tendrán su lugar en la creación y ruptura de programas.

  • Antron Argaiv dice:

    C: el lenguaje avanzado con la flexibilidad del lenguaje ensamblador combinado con la legibilidad y facilidad de uso del lenguaje ensamblador. Una gran flexibilidad conlleva una gran responsabilidad ... en este caso, asegurarse de que cualquier aritmética que haga con ese puntero de función resulte en algo que muestre una función válida ...

    El hecho de que * puedas * hacer algo así no significa que * tengas que hacerlo *.

    • Daniel dice:

      El estándar de lenguaje C no le permite hacer aritmética en punteros funcionales.

      • Antron Argaiv dice:

        Desafío aceptado 🙂

    • Thomas dice:

      C: el equipo portátil.

    • Murray dice:

      En su mayoría, evite la programación "Listo", pero a veces debe abrir los opps de engranajes especiales. Una vez creé una máquina virtual personalizada que se ejecutaba dentro de un micro para permitir algún PLC como comportamiento dentro de una caja de arena estrictamente controlada. La VM necesitaba un intérprete y, en lugar de una instrucción de conmutación, se utilizó el primer byte de la instrucción para llamar a una función, referenciada en una matriz de punteros de función constante. El resultado fue un micro con una máquina virtual en el interior que tenía instrucciones muy CISKy, esencialmente instrucciones API, lo que permite que la pila TCP / IP y otras funciones constantes rápidas se operen de forma nativa, mientras que el código de comportamiento del usuario final se puede escribir en un lenguaje avanzado con API como instrucciones. . Una llamada funcional basada en punteros habilitó el diseño. Debido al rendimiento requerido, la antigua declaración de cambio no funcionaría aquí.

  • codigos dice:

    todo esto y ninguna explicación de los mecanismos de revocación utilizados por ejemplo en las bibliotecas ...

  • Daños severos a los neumáticos dice:

    "El hecho de que * puedas * hacer algo así no significa que * tengas que hacerlo".

    Muy cierto. Simplemente diría que un gran poder conlleva una gran responsabilidad.

    Y: "si no soportas el calor, no vayas a la cocina"

    Ciertamente uso los indicadores de función con moderación y pocos programas que escribo tienen algún propósito para ellos. Pero para el pirata informático, son una herramienta poderosa, por ejemplo, si desea ejecutar código en una ROM o hacer cosas que nunca tuvo la intención de hacer. Como se mencionó, su uso principal en la programación convencional proporciona rutinas de recuperación a las bibliotecas.

    Es bueno ver que estos tutoriales básicos de C despiertan interés en el lenguaje C.

    • Ostraco dice:

      "Es bueno ver que estos libros de texto básicos de C despiertan interés en el lenguaje C."

      Las personas que trabajan en código abierto suelen tener que hacerlo.

  • GuruBuckaroo dice:

    En mis 30 años de programación C / C ++, tuve que usar un puntero a una función exactamente una vez. Ni siquiera recuerdo las circunstancias o por qué tuvo que hacerse de esa manera, solo recuerdo que apenas tiene sentido y prometí evitarlo a toda costa en el futuro.

    • Vejestorio dice:

      La implementación de objetos con herencia y sobrecarga es una aplicación común de punteros funcionales. Callacks es otro. Pero, por supuesto, debería estar bellamente abstraído. Aritmética con punteros funcionales no me atrevería 🙂

      • daid303 dice:

        Si quiere C ++, simplemente use C ++ en lugar de ejecutar su propio C ++, mal.

        • es verdad dice:

          Dudo que las personas que usan revocaciones o cualquier plantilla de pseudocarga / OO en C lo hagan por diversión.
          C ++ tiene un tiempo de ejecución, por ejemplo, y en algunos casos no es deseable, en cuyo caso usar solo C es bastante bueno. Mire cómo se escriben los controladores de dispositivo, llena una estructura con punteros funcionales.

        • Artenz dice:

          Utilizo muchas funciones en C en objetos incrustados, pero no estoy interesado en absoluto en usar C ++, donde obtener un control adecuado sobre la salida requiere mucho más esfuerzo.

      • Jim dice:

        Dodo tiene razón. Lo hicimos hace mucho tiempo cuando C ++ era un brillo en los ojos de alguien que miraba el horizonte.

        Sí, implementamos con mucho éxito la programación orientada a objetos en K&R C para un gran proyecto, y los indicadores funcionales fueron una parte importante de ese éxito.

    • poco dice:

      bsearch (), ¿tal vez?

    • daid303 dice:

      En mis 20 años de programación en C / C ++, tuve que usar un puntero a una función en casi todos los proyectos. Ni siquiera recuerdo las circunstancias en las que no sería un gran lío no comprobable sin ellos, con interdependencias en todas partes.
      Los mecanismos de revocación son habituales. Y si nunca ha usado un puntero funcional durante 30 años de programación C / C ++, entonces realmente se está perdiendo un buen diseño de programa.

    • rnjacobs dice:

      Ser capaz de crear matrices de funciones es preguntarse útil.

    • FredTheRanger dice:

      la función qsort () requiere un puntero a la función de comparación.

    • el demonista dice:

      Si está haciendo algo con módulos de tiempo de ejecución de complemento u objetos opacos, es casi necesario utilizar punteros de función.

    • Torsten Martinsen dice:

      ¿En serio? Tampoco usas std :: function o lambdas, ¿entonces?

  • Ostraco dice:

    "MEMORIAS DIRECTAMENTE EJECUTIVAS"

    En su lugar, deberíamos intentar rehabilitarlos.

    • Moryc dice:

      Cuando se trata de C, esperemos que la ejecución sea al menos indolora; ningún recuerdo debería sufrir debido a C ...

    • Elliot Williams dice:

      ¿Si claro?

      Un tema distante: ¿cómo esas dos palabras significan lo mismo de todos modos?

      • Tiza aburrido dice:

        La ejecución como término legal se utilizó para ejecutar la sentencia del tribunal. Luego, los tribunales ejecutaron públicamente la sentencia de muerte. La palabra llegó a conocerse familiarmente por matar a alguien y, finalmente, se convirtió en el término legal para designarla.

  • snarkysparky dice:

    ¿Cómo se pueden realizar operaciones aritméticas con punteros de función cuando el tamaño del objeto que se muestra en "la función" no es constante?

    • Sven Gregori dice:

      Probablemente debería incluir una oración o dos sobre eso.

      Respuesta corta: no puedes.

      La aritmética de punteros solo es posible con punteros de datos cuando se conocen el tipo de datos subyacentes y su tamaño. Los punteros de función junto con el puntero vacío no logran esto: sizeof (function_name) y sizeof (void) no son válidos en C estándar, pero GCC (y Clang, ¿quizás otros también?) Aún lo permite estableciendo el resultado como 1 (agregando aunque la bandera -pedantic o -Wpointer-arith lo advertirá). Entonces, con estos compiladores es posible escribir fptr + 1, pero esto simplemente aumentará la dirección de la función en un byte.

  • Krunch3r dice:

    Los indicadores funcionales me ayudan a objetivar mi código C (que es mi estilo) y debido a que su artículo los toca, siempre está listo para sortear una condición descartada, en caso de que ayude. El conjunto de punteros funcionales fue algo que olvidé, pero su artículo lo trajo de vuelta a la vanguardia. No hago ingeniería inversa ni pruebas de plumas, pero probablemente lo haré y ahora tengo un grupo de expertos adicional. Gracias. Espero que el blog algún día ingrese la palabra clave restrictiva que apareció en este artículo sobre cómo C no es necesariamente de bajo nivel: https://queue.acm.org/detail.cfm?id=3212479

    • Sven Gregori dice:

      Hola 🙂

      Ciertamente vale la pena mencionar la palabra clave restrictiva, no estoy seguro de si proporcionará lo suficiente para su propio artículo, pero podría tener un lugar en alguna pieza de "calificadores específicos de punteros". Teniendo en cuenta que he omitido por completo la palabra clave const, definitivamente hay espacio.

  • Steve dice:

    AMO los punteros de función.

    Recientemente los usé en un proyecto de matriz de LED para permitir el cambio del "tipo" de píxel sin la necesidad de un gran caso / si-entonces-otro.

    Simple 'for loops' para XY y llame a GetPixel[PixelType](X, Y), donde PixelType selecciona el 'tipo' de píxel. Aleatorio, 'llama', 'humo', 'destellos', 'rampas', de imágenes, etc.

    El contenido de cada versión de "GetPixel" puede ser muy diferente, pero la legibilidad del bucle de la imagen principal permanece MUY clara.

  • Sparkygsx dice:

    Los punteros funcionales se utilizan ampliamente en sistemas integrados, más obviamente para matrices de vectores disruptivos. La mayoría de las computadoras en este mundo no tienen pantallas y teclados conectados, y los sistemas integrados es donde C sigue siendo el idioma dominante, y probablemente lo será durante muchos años.

  • Robot dice:

    Guau. Esto es realmente interesante. Escribí C para. . . Supongo que hace 20 años (un nivel muy, muy difícil sin entrenamiento formal) y no lo sabía. Cosas buenas, hay tantas formas de usar y abusar de esto: D

    - robot

  • Alan Kilian dice:

    Consulte la regla n. ° 9 de las pautas de la NASA / JPL sobre Power Ten para obtener un código confiable.

    https://en.m.wikipedia.org/wiki/The_Power_of_10:_Rules_for_Developing_Safety-Critical_Code

    Si es suficiente para JPL .....

    • kdev dice:

      Modificaría la regla # 9 para decir "evitar / dinámicos / operar punteros". El uso de punteros funcionales puede ser esencial para evitar un desorden de espaguetis de copiar y pegar y los inevitables errores de copiar y pegar que los acompañan. Por ejemplo, preferiría depurar código con muchas llamadas a
      doComplexTaskOnArray (specificBehaviorFunction), usando diferentes punteros de BehaviorFunction específicos,
      ol
      doComplexTaskOnArray ()
      doASlightlyDifferentComplexTaskOnArray ()
      doComplexTaskOnArrayButOneThingIsIsIgnored ()
      etc ...

      Cuando comienzas a cambiar dinámicamente los punteros funcionales, las cosas se vuelven peligrosas. De hecho, las reglas de MISRA C prohíben los punteros * no constantes * a funciones. Siempre que las posibles direcciones de omisión sigan siendo pequeñas, estáticas y predecibles, hay pocas posibilidades de daño y muchas de ganar en términos de modularidad y legibilidad.

      • Alan Kilian dice:

        ¿En qué se diferencian sus dos ejemplos?

        Tienen idéntica complejidad, pero el compilador no puede realizar ninguna comprobación de parámetros.

        ¿Y este es el que prefieres?

        • kdev dice:

          El primero sería un archivo con una única función compleja grande con algunas funciones auxiliares pequeñas insertadas por punteros de función, y el segundo sería un archivo lleno de funciones complejas grandes que solo se diferencian entre sí por unas pocas líneas o incluso unos pocos caracteres. .

          El primero es lo suficientemente simple de leer, comprender y depurar. El segundo es una pesadilla de cuidados. Lo sé por experiencia de primera mano.

          Tampoco estoy seguro de por qué cree que el compilador no puede verificar los parámetros. GCC ciertamente me advierte si intento usar accidentalmente un puntero funcional con argumentos diferentes a los esperados.

    • Elliot Williams dice:

      Pero la razón de estas reglas es permitir estática control. La mitad de la razón para usar punteros de función es incorporar algún comportamiento dinámico / en tiempo de ejecución en el programa.

      Solo está diciendo que los requisitos de la crítica de seguridad de JPL y los que uso para mi divertido robot de juguete son diferentes.

      Pero asegúrese de escribir una C compatible con MISRA para sus proyectos de pasatiempos si lo desea. Encuentro que se necesita el mayor placer de la programación para imponer límites tan rígidos.

      ¡No puedes deletrear punteros de función sin "diversión"!

  • Puntilla dice:

    Mi cabeza estalló.

  • Independiente dice:

    No mencionó el número uno anterior para los indicadores funcionales: los optimizadores los odian y, por lo general, no se conecta a Internet.

    • F dice:

      No es posible tener un puntero a una función en línea porque la función en línea no existe en el código del objeto.

      Mi experiencia con gcc a lo largo de los años es que la diferencia entre -O0 y -O3 apenas se puede medir para la mayoría de los programas, así que a quién le importa lo que piense el optimizador.

      • Somun dice:

        Afortunadamente, tu experiencia no se generaliza. La optimización genera un rendimiento extremadamente diferente. A veces, varios pedidos.

        • Optimizador dice:

          Estoy de acuerdo. Para mí, pasar de O0 a O3 hace una GRAN diferencia, porque todos los programas que escribo en C involucran tanto número científico como velocidad.

    • rubypanther dice:

      Esa es una de las razones por las que apoyo firmemente las macros previas al juicio; entonces la gente me odia mucho, pero al compilador parece gustarle. ¡Sé de qué lado se unta el pan con mantequilla!

  • Garolo dice:

    ahora imagina que tienes un compilador para tu plataforma, en tu programa, y ​​generas un código, lo compilas, lo cargas en la memoria y saltas a él. bam, ahora tienes JIT (simple) implementado.

    • David Blundell dice:

      Estaba pensando en este enfoque para un dispositivo CANbus extremadamente crítico en el que estoy trabajando. Como parte del análisis del archivo de configuración, "compile" las reglas de configuración en tablas de código y ejecútelas con tablas de punteros de función. Sin embargo, me asusta mucho. No estoy entrenado formalmente y eso parece lleno de peligro. Pero también parece tener el potencial de aumentar considerablemente la velocidad de ejecución en comparación con la interpretación de la configuración durante el tiempo de ejecución.

      • Garolo dice:

        el archivo de configuración no cambia muy a menudo, presumiblemente, y probablemente pueda comenzar a funcionar por un tiempo. así que al principio, convierta el archivo de configuración a C, compílelo en una biblioteca compartida con una interfaz estandarizada y cárguelo dinámicamente. eso será tan seguro como lo logrará C.

    • F dice:

      Ahora imagine un lenguaje interpretado en el que cada línea de código posible que desee ejecutar ya esté presente en el programa de interpretación, y simplemente elija cuál de ellos desea ejecutar con su script. El intérprete generalmente usa una tabla hash de punteros funcionales para rastrear cada línea de código que puede ejecutar. Con el lenguaje interpretado, puede llamar a funciones C compiladas si tiene la firma de la función. Puede usar dlsym () para obtener el puntero a la función, y luego puede usar la biblioteca libffi para generar mágicamente lógica de llamada funcional durante el tiempo de ejecución. El esquema guile tiene una excelente implementación de todas estas cosas, es muy interesante poder invocar en C funciones de un intérprete sin tener que usar una biblioteca de pegamento C como un trago.

      • rubypanther dice:

        En el lenguaje ruby ​​funciona así por defecto; no está utilizando una biblioteca de pegamento externa o un lenguaje de pegamento especial, en su lugar, el intérprete se implementa en C y proporciona una API para registrar funciones de C (a través de un puntero) y decirles cuál es su tipo de OO. Y esos tipos también son tipos C. Así que tiene un enfoque bidireccional completo. Incluso puede incorporar las partes C, pero cuando normalmente tiene que incluir ruby.h, la mayor parte del tiempo no ofrece muchas ventajas.

        El problema con swig es que es una herramienta general, por lo que nunca coincide con fluidez o genera algo fácil de leer, pero con Ruby solo termino con un archivo .c con mis funciones C, y un poco más de 1 línea de código de pegado por función. ; y el pegamento es C normal sin dependencias adicionales, y vive en el archivo .c de mi programa.

    • eristristofersono dice:

      Estoy interesado en cualquier artículo o sitio web que la gente pueda recomendar sobre varias formas de hacer esto. Lo hice una vez y me divertí mucho con él, pero realmente no asimilé lo que hace el código que copié y pegué del sitio web (¡y ahora no lo encuentro!).

      Básicamente, me gustaría conocer al menos algunos tipos diferentes de generación de código dinámico en tiempo de ejecución, y los pros y los contras. Por ejemplo, sé que hay algunos lenguajes dinámicos que compilan su código con un compilador insertado en la implementación del lenguaje dinámico. Algunos de estos de alguna manera generan una fuente C o un código de bits LLVM y luego usan LLVM (¿se usa como una biblioteca dentro del programa? ¿O invocando programas externos clang / LLVM?) Para insertar de alguna manera el código generado en el programa; luego hay otros (google "ruby 3 × 3" para ver un ejemplo) donde el programa genera C o similar, luego llama a un compilador externo (gcc o pcc, por ejemplo) para compilar el código solo en memoria, luego de alguna manera lo pone en el programa original (a través de dlsym? ¿Todas las demás formas de hacerlo?). Asumiría que hay otros programas como el mío que solo generan bytes de instrucción en lenguaje de máquina como bits como lo hizo mi programa.

      Finalmente, me gustaría saber qué debe hacer en Intel y otros sistemas para marcar las páginas como viables, qué estrategias de asignación de memoria debe usar, etc.

      • ospr3y dice:

        podría compilar su fuente C en un archivo de objeto (no con todas las funciones) y leerlo y analizar los campos mismos y vincular una ubicación según sea necesario. Sin embargo, lo que debe recordar es que el archivo de objeto puede contener referencias a símbolos en su ejecutable (funciones de biblioteca, por lo menos). Hice esto una vez para copiar dlopen / dlsym en djgcc.

        dlopen / dlsym funciona muy bien para usted, por lo que le recomiendo encarecidamente usarlos si están disponibles para usted.

      • ospr3y dice:

        realmente recordando algo más que hice en ese momento ... si el código C que desea importar no contiene referencias a datos estáticos o globales, entonces puede cargar el archivo de objeto en una tabla de datos y simplemente realizar las funciones. allí en relación con el comienzo de la tabla de datos. Puede encontrar la dirección relativa de las funciones haciendo un descarte simbólico del archivo objeto (use "nm" y busque símbolos de texto). Esto depende del compilador que genera el código independiente de la posición (PIC).

        Pensando un poco más en esto ... es posible que desee hacer que su programa genere un script de enlace con el comienzo del segmento de "texto" (código) establecido en la dirección real de la tabla de datos en la que desea cargar el código generado. . Con esto también podría definir símbolos en el script de enlace para glóbulos. Luego, vincula su objeto a la ejecución absoluta, lo lee en su matriz y lo ejecuta.

        Pero como dije antes, dlopen / dlsym hace todo esto por ti

  • BrendaEM dice:

    Los punteros no son tan necesarios como podría pensarse. No los necesitaba en ningún programa de Arduino que escribí.

    • Artenz dice:

      He estado usando punteros de función durante 30 años y nunca he escrito ningún software Arduino. El mundo es más grande de lo que piensas.

      • David Blundell dice:

        Amén.

    • Matheus Petry dice:

      Crees que no los necesitas, pero a menos que nunca hayas usado un arreglo, los has usado, un "lenguaje" arduo también usa algunas compilaciones dependiendo de la biblioteca que estés usando, eso es C ++, y un un poco de magia, que no ves detrás. que usan punteros.

    • artaggg dice:

      ¿Nunca ha utilizado attachInterrupt ()?

      esto toma un puntero funcional como argumento.

  • metano dice:

    Tenga en cuenta que el puntero de función no siempre apunta a la ubicación de la memoria del código de máquina. Esto es cierto, por ejemplo, para el hardware de computadora, pero hay arquitecturas menos comunes en las que un puntero de función muestra un descriptor de función que es una estructura pequeña con punteros a una ubicación de memoria del código de máquina. Una vez que empiezas a investigar la programación en C a nivel de arquitectura del sistema, las cosas se vuelven un poco más complicadas y escribir código realmente portátil es una tarea bastante complicada.

    • F dice:

      ¿Cuáles son estas arquitecturas menos comunes? Supongo que son todos oscuros proyectos de supercomputadoras como Cray o Stardent o Sequent que ahora están todos muertos. He trabajado con 68000, SPARC, PowerPC, ARM, ARM64, MIPS, MIPS64, AMD64, x86, Itanium, Alpha y HPPA en código C de bajo nivel y nunca había visto uno como ese.

      • metano dice:

        Que yo recuerde, el IA64 usó descriptores de función en distribuciones de Linux más antiguas para lograr un código independiente de la posición y hay una ABI para ppc64le que también lo tiene. Y esto generalmente está oculto para el desarrollador, no lo golpeará a menos que desee descartar el código de máquina de la memoria o necesite asignar un búfer, llenarlo con código de máquina y luego ejecutarlo.

  • yetihehe dice:

    Los punteros a funciones son adecuados para realizar fread / fwrite en su avr. Entonces puede hacer printf a lcd o incluso a su propio dispositivo externo:

    int mywrite (signo c, ARCHIVO * fd) {
    loop_to_the_bit_is (SPSR, SPIF);
    SPDR = c;
    return 0;
    }
    // int myread (FILE * fd) {/ * lee desde el dispositivo y regresa como int o devuelve FDEV_ERR si falla * /}

    ARCHIVO * myfile = fdevopen (mywrite, NULL);

    fprintf (mi archivo, "% d", 42); // escribe "42" en spi

  • xorpunk dice:

    el uso después de la ejecución de código libre y de desbordamiento de pila generalmente resulta de que las personas hacen esto y no comprenden lo que está sucediendo en la memoria.

  • Steve dice:

    Me gustó mucho este artículo. Busque más como él.
    La mayoría de las personas no conocen el lenguaje ensamblador, por lo que no comprenden todas las cosas que hacen los lenguajes avanzados para hacer lo que es simple (y común) en el código ensamblador.

  • snaslund dice:

    Los punteros tienden a ser una de las mayores fuentes de errores de programación. Muchos lenguajes más nuevos, como Java, se han diseñado específicamente para prohibir el uso de punteros. Tienen su objetivo, pero saben que probablemente son una fuente de errores de programación. Personalmente si tengo manera de evitar el uso de punteros, lo hago.

    • Miguel dice:

      ¡Así es! Si alguien está escribiendo un programa de misión crítica, una de las reglas de los testimonios no son los indicadores. Hay una buena razón para ello. La mayoría de la gente simplemente no sabe cómo usarlos correctamente.

      • ospr3y dice:

        ¿Entonces esto significa que no puede usar matrices o referencias a estructuras como argumentos para funciones?

        Una de las principales razones por las que tenemos errores en los programas se debe a las reglas impuestas a los desarrolladores que no están familiarizados con el compilador. Si no le gustan los punteros, no use C o C ++.

        • Greenaum dice:

          No, solo significa que muchos lenguajes implementan tales cosas en el compilador con algo de código para administrar las cosas, en lugar de arrojarse a una memoria simple como C tiende a hacer. Significa que los idiomas tienen ... ¡clases! Incluso los tipos en C no están particularmente mecanografiados, por lo que la conversión funciona. Todo es solo RAM y los punteros son parte del código de máquina de la mayoría de las CPU.

          C fue especialmente bueno durante las dos primeras décadas de escritura de sistemas operativos, especialmente en máquinas con 8K de kernel, tal vez hasta 512K de RAM (para elegir un número de la nada). Pero para las cosas modernas más complejas es desesperadamente peligroso y a prueba de errores, no hay necesidad de trabajar tan cerca del metal desnudo sin algunos protectores de seguridad. Siempre es posible hacer una tremenda trampa en C, no existe una forma infalible de protegerse de ello.

          Como alternativa al asm, como dice el refrán, es bastante bueno, pero realmente tiene que permanecer en su oscuro y terrible nicho.

          Apuesto a que no obtendría todas estas cosas "virales" si todos usáramos COBOL.

    • xorpunk dice:

      Usaría Rust and Go como ejemplos más apropiados. Cada vez que un investigador mira un motor JRE, hay un gran conjunto de divulgaciones de seguridad.

      Por cierto, yo era uno de los idiotas que hacía J2ME para comprobaciones integradas desde 1999-2006. No extraño esa horrible sintaxis de clase

      • Slartibart dice:

        Bueno, Go and Rust intenta capturar a los usuarios del sistema y tratar de evitar que hagan cosas estúpidas simplemente revelándolo, al igual que Java, por lo que es solo una cuestión de exposición y tiempo antes de que los edificios a prueba de idiotas en ellos sean descubierto.

        • Artenz dice:

          Podría escribir un intérprete de C en Rust y luego usarlo para ejecutar un programa en C con punteros de función.

    • Slartibart dice:

      Erm ... bueno, en Java y C # cada función se realiza con un puntero (todas las llamadas son efectivamente virtuales), mientras que en C (y C ++) esto se suele calcular (a menos que sea virtual) en tiempo de compilación.

    • ospr3y dice:

      Se proyectó Java - ¡¡¡RAOFL !!!

      Java fue creado como un lenguaje interpretado que supuestamente funcionaba en muchas plataformas diferentes. Los indicadores de permisos romperían este GRAN TIEMPO !!! No escuches nada de la historia revisionista, todo es una tontería.

      • xorpunk dice:

        JVM está escrito en ANSI C. Pero una historia genial

        Las divulgaciones de seguridad famosas por ello casi nunca se encuentran en el lado abstracto del motor en tiempo de ejecución.

  • MarkF dice:

    Los punteros funcionales son invaluables para una emulación rápida, simplemente crea un puntero funcional grande para buscar una tabla para asignar códigos de operación a sus respectivos manejadores. Luego, cuando se cambia a C ++, puede comenzar a mezclarlo con extensiones no estándar como el operador de dirección dual y longjmp, etc. para reemplazar las llamadas con saltos y relajar más rápidamente la pila cuando el controlador haya terminado.

  • adr dice:

    no se garantiza que sizeof (vacío *) sea sizeof (vacío)

    • ()); una tarea entre los dos es UB. ejemplo

      dice:
      tamaño de (vacío *) == tamaño de (vacío (**) ());

  • Tenga en cuenta que se trata de dos estrellas. El puntero de función puede ser más grande que un puntero de objeto, pero es en sí mismo un objeto, por lo que un puntero al puntero de función es un puntero de objeto. y Dave

    dice:

  • mojosa

Victoria Prieto
Victoria Prieto

Deja una respuesta

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