Rumah  >  Artikel  >  pembangunan bahagian belakang  >  C Reflection Magic: Pembalakan Mudah dengan Pembungkus untuk Mencetak Argumen dan Keputusan Fungsi Arbitrari

C Reflection Magic: Pembalakan Mudah dengan Pembungkus untuk Mencetak Argumen dan Keputusan Fungsi Arbitrari

WBOY
WBOYasal
2024-07-18 20:20:31448semak imbas

C Reflection Magic: Simple Logging with A Wrapper for Printing Arbitrary Functions Arguments and Results

Artikel ini ialah laporan penyelidikan yang merangkumi beberapa aspek pelaksanaan yang berpotensi untuk menulis pembalut pembantu yang secara automatik akan mencatatkan hujah dan hasil fungsi C sewenang-wenangnya. Ini adalah salah satu contoh mengapa refleksi mungkin berguna walaupun dalam C. Pelaksanaannya adalah berdasarkan projek Metac. Pengenalannya telah diberikan dalam artikel ini. Penyelidikan mempunyai beberapa hasil yang baik, tetapi masih dalam proses. Komen tentang cara ia boleh dilakukan dengan cara yang lebih baik adalah dihargai.

Pengelogan ialah salah satu cara penting untuk menyahpepijat. Membuat pengelogan yang betul adalah kunci untuk memahami perkara yang mungkin berlaku tanpa menggunakan penyahpepijat. Tetapi ia menjengkelkan untuk mencetak semua hujah bagi setiap fungsi dan hasilnya. Refleksi C dengan Metac berkemungkinan mempunyai keupayaan untuk melakukan ini, kerana maklumat penyahpepijatan yang disediakan oleh DWARF mempunyai semua data tentang jenis setiap hujah. Semak ia keluar. Berikut ialah aplikasi ujian:

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>

#include "metac/reflect.h"

int test_function1_with_args(int a, short b) {
    return a + b + 6;
}
METAC_GSYM_LINK(test_function1_with_args);

int main() {
    printf("fn returned: %i\n", test_function1_with_args(1, 2));

    return 0;
}

Kami ingin membuat beberapa jenis pembungkus untuk mencetak hujah test_function1_with_args. Metac akan menjana maklumat pantulannya sejak METAC_GSYM_LINK(test_function1_with_args); ada dalam kod. Untuk kesederhanaan int dan jenis hujah pendek dipilih. Idea pertama bagaimana kita boleh membuat pembungkus ialah - mencipta makro:

void print_args(metac_entry_t *p_entry, ...) {
// use va_args and debug information about types to print value of each argument
}

#define METAC_WRAP_FN(_fn_, _args_...) ({ \
        print_args(METAC_GSYM_LINK_ENTRY(_fn_), _args_); \
        _fn_(_args_); \
    })

int main() {
    // use wrapper instead of printf("fn returned: %i\n", test_function1_with_args(1, 2));
    printf("fn returned: %i\n",
        METAC_WRAP_FN(test_function1_with_args, 1, 2));

    return 0;
}

Pembungkus ini setakat ini hanya mengendalikan hujah, tetapi ok untuk langkah pertama. Mari cuba laksanakan print_args. Inilah percubaan naif pertama:

void print_args(metac_entry_t *p_entry, ...) {
    if (p_entry == NULL || metac_entry_has_paremeter(p_entry) == 0) {
        return;
    }

    va_list args;
    va_start(args, p_entry);

    printf("%s(", metac_entry_name(p_entry));

    // output each argument
    for (int i = 0; i < metac_entry_paremeter_count(p_entry); ++i) {
        if (i > 0) {
            printf(", ");
        }

        // get i-th arg
        metac_entry_t * p_param_entry = metac_entry_by_paremeter_id(p_entry, i);
        if (metac_entry_is_parameter(p_param_entry) == 0) {
            // something is wrong
            break;
        }
        // if it’s … argument just print … - there is no way so far to handle that
        if (metac_entry_is_unspecified_parameter(p_param_entry) != 0) {
            // we don't support printing va_args... there is no generic way
            printf("...");
            break;
        }

        // get arg name and info about arg type
        metac_name_t param_name = metac_entry_name(p_param_entry);
        metac_entry_t * p_param_type_entry = metac_entry_parameter_entry(p_param_entry);
        if (param_name == NULL || param_name == NULL) {
            // something is wrong
            break;
        }

        // lets handle only base_types for now
        if (metac_entry_is_base_type(p_param_type_entry) != 0) {
            // take what type of base type it is. It can be char, unsigned char.. etc
            metac_name_t param_base_type_name = metac_entry_base_type_name(p_param_type_entry);

// if _type_ is matching with param_base_type_name, get data using va_arg and print it.
#define _base_type_arg_(_type_, _pseudoname_) \
    do { \
        if (strcmp(param_base_type_name, #_pseudoname_) == 0) { \
            _type_ val = va_arg(args, _type_); \
            metac_value_t * p_val = metac_new_value(p_param_type_entry, &val); \
            if (p_val == NULL) { \
                break; \
            } \
            char * s = metac_value_string(p_val); \
            if (s == NULL) { \
                metac_value_delete(p_val); \
                break; \
            } \
            printf("%s: %s", param_name, s); \
            free(s); \
            metac_value_delete(p_val); \
        } \
    } while(0)
    // handle all known base types
    _base_type_arg_(char, char);
    _base_type_arg_(unsigned char, unsigned char);
    _base_type_arg_(short, short int);
    _base_type_arg_(unsigned short, unsigned short int);
    _base_type_arg_(int, int);
    _base_type_arg_(unsigned int, unsigned int);
    _base_type_arg_(long, long int);
    _base_type_arg_(unsigned long, unsigned long int);
    _base_type_arg_(long long, long long int);
    _base_type_arg_(unsigned long long, unsigned long long int);
    _base_type_arg_(bool, _Bool);
    _base_type_arg_(float, float);
    _base_type_arg_(double, double);
    _base_type_arg_(long double, long double);
    _base_type_arg_(float complex, complex);
    _base_type_arg_(double complex, complex);
    _base_type_arg_(long double complex, complex);
#undef _base_type_arg_
        }
    }
    printf(")\n");
    va_end(args);
    return;
}

Jika kita menjalankannya kita akan melihat:

% ./c_print_args
test_function1_with_args(a: 1, b: 2)
fn returned: 9

Ia berkesan! Tetapi ia hanya mengendalikan jenis asas. Dan kami mahu ia bersifat universal.

Cabaran utama di sini ialah dengan baris ini:

 _type_ val = va_arg(args, _type_); 

Makro va_arg C memerlukan jenis argumen untuk diketahui pada masa penyusunan. Walau bagaimanapun, maklumat pantulan hanya menyediakan nama jenis pada masa jalan. Bolehkah kita menipunya? va_arg ialah makro yang merangkumi fungsi terbina. Parameter kedua ialah jenis (perkara yang sangat tidak tipikal). Tetapi mengapa perkara ini sama sekali memerlukan jenis? Jawapannya ialah - untuk memahami saiz dan dapat mengambilnya dari timbunan. Kita perlu meliputi semua saiz yang mungkin dan untuk mendapatkan penunjuk kepada hujah seterusnya. Di sebelah Metac, kita tahu saiz hujah - kita boleh menggunakan coretan ini untuk mendapatkannya:

        metac_size_t param_byte_sz = 0;
        if (metac_entry_byte_size(p_param_type_entry, &param_byte_sz) != 0) {
            // something is wrong
            break;
        }

Sebagai idea seterusnya mari kita buat makro yang akan meliputi 1 saiz dan pastikan kita mengendalikannya dengan betul:

        char buf[32];
        int handled = 0;
#define _handle_sz_(_sz_) \
        do { \
            if (param_byte_sz == _sz_) { \
                char *x = va_arg(args, char[_sz_]); \
                memcpy(buf, x, _sz_); \
                handled = 1; \
            } \
        } while(0)
        _handle_sz_(1);
        _handle_sz_(2);
        _handle_sz_(3);
        _handle_sz_(4);
// and so on ...
        _handle_sz_(32);
#undef _handle_sz_

Dengan pendekatan ini, kami merangkumi saiz yang berbeza dari 1 hingga 32. Kami boleh menjana kod dan merangkumi argumen bersaiz sehingga mana-mana nombor sewenang-wenangnya, tetapi dalam kebanyakan kes orang menggunakan penunjuk dan bukannya menghantar tatasusunan/struktur secara terus. Demi contoh kami, kami akan mengekalkan 32.
Mari memfaktorkan semula fungsi kita untuk menjadikannya lebih boleh digunakan semula membahagikannya kepada 2 vprint_args dan print_args sama seperti 'vprtintf' dan printf:

void vprint_args(metac_tag_map_t * p_tag_map, metac_entry_t *p_entry, va_list args) {
    if (p_entry == NULL || metac_entry_has_paremeter(p_entry) == 0) {
        return;
    }

    printf("%s(", metac_entry_name(p_entry));

    for (int i = 0; i < metac_entry_paremeter_count(p_entry); ++i) {
        if (i > 0) {
            printf(", ");
        }

        metac_entry_t * p_param_entry = metac_entry_by_paremeter_id(p_entry, i);
        if (metac_entry_is_parameter(p_param_entry) == 0) {
            // something is wrong
            break;
        }
        if (metac_entry_is_unspecified_parameter(p_param_entry) != 0) {
            // we don't support printing va_args... there is no generic way
            printf("...");
            break;
        }

        metac_name_t param_name = metac_entry_name(p_param_entry);
        metac_entry_t * p_param_type_entry = metac_entry_parameter_entry(p_param_entry);
        if (param_name == NULL || p_param_type_entry == NULL) {
            // something is wrong
            break;
        }

        metac_size_t param_byte_sz = 0;
        if (metac_entry_byte_size(p_param_type_entry, &param_byte_sz) != 0) {
            // something is wrong
            break;
        }

        char buf[32];
        int handled = 0;
#define _handle_sz_(_sz_) \
        do { \
            if (param_byte_sz == _sz_) { \
                char *x = va_arg(args, char[_sz_]); \
                memcpy(buf, x, _sz_); \
                handled = 1; \
            } \
        } while(0)
        _handle_sz_(1);
        _handle_sz_(2);
//...
        _handle_sz_(32);
#undef _handle_sz_

        if (handled == 0) {
            break;
        }

        metac_value_t * p_val = metac_new_value(p_param_type_entry, &buf);
        if (p_val == NULL) {
            break;
        }
        char * v = metac_value_string_ex(p_val, METAC_WMODE_deep, p_tag_map);
        if (v == NULL) {
            metac_value_delete(p_val);
            break;
        }
        char * arg_decl = metac_entry_cdecl(p_param_type_entry);
        if (arg_decl == NULL) {
            free(v);
            metac_value_delete(p_val);
            break;
        }

        printf(arg_decl, param_name);
        printf(" = %s", v);

        free(arg_decl);
        free(v);
        metac_value_delete(p_val);

    }
    printf(")");
}

void print_args(metac_tag_map_t * p_tag_map, metac_entry_t *p_entry, ...) {
    va_list args;
    va_start(args, p_entry);
    vprint_args(p_tag_map, p_entry, args);
    va_end(args);
    return;
}

Pembaca mungkin perasan bahawa kami menambah p_tag_map sebagai hujah pertama. Ini adalah untuk penyelidikan lanjut - ia tidak digunakan dalam artikel ini.

Mari cuba buat bahagian yang mengendalikan hasilnya. Malangnya typeof tidak disokong sehingga C23 (sambungan gcc sebagai pilihan, tetapi ia tidak akan berfungsi dengan denting) dan kami menghadapi dilema - adakah kami mahu mengekalkan notasi METAC_WRAP_FN kami seperti sedia ada, atau tidak mengapa untuk meluluskannya satu hujah lagi - jenis hasil fungsi yang akan digunakan sebagai penimbal. Mungkin kita boleh menggunakan libffi untuk mengendalikan perkara ini secara universal - Metac mengetahui jenisnya, tetapi tidak jelas cara meletakkan data yang dikembalikan ke dalam penimbal dengan saiz yang betul. Untuk memudahkan, mari tukar makro kami:

#define METAC_WRAP_FN_RES(_type_, _fn_, _args_...) ({ \
        printf("calling "); \
        print_args(NULL, METAC_GSYM_LINK_ENTRY(_fn_), _args_); \
        printf("\n"); \
        WITH_METAC_DECLLOC(loc, _type_ res = _fn_(_args_)); \
        print_args_and_res(NULL, METAC_GSYM_LINK_ENTRY(_fn_), METAC_VALUE_FROM_DECLLOC(loc, res), _args_); \
        res; \
    })

Sekarang kami menyerahkan _type_ sebagai hujah pertama untuk menyimpan hasilnya. Jika kami memberikan jenis atau argumen yang salah - pengkompil akan mengadu tentang _type_ res = _fn_(_args_) ini. Ini bagus.
Mencetak hasilnya adalah tugas yang remeh, kami sudah melakukannya dalam artikel pertama. Mari juga mengemas kini fungsi ujian kami untuk menerima beberapa jenis parameter yang berbeza.
Berikut ialah kod contoh terakhir.

Jika kami menjalankannya, kami akan mendapat ulasan:

% ./c_print_args

# show args of base type arg function
calling test_function1_with_args(int a = 10, short int b = 22)
fn returned: 38

# show args if the first arg is a pointer
calling test_function2_with_args(int * a = (int []){689,}, short int b = 22)
fn returned: 1710

# using METAC_WRAP_FN_RES which will print the result. using pointer to list
calling test_function3_with_args(list_t * p_list = (list_t []){{.x = 42.420000, .p_next = (struct list_s []){{.x = 45.400000, .p_next = NULL,},},},})
fn returned: 87.820000

# another example of METAC_WRAP_FN_RES with int * as a first arg
calling test_function2_with_args(int * a = (int []){689,}, short int b = 22)
test_function2_with_args(int * a = (int []){689,}, short int b = 22) returned 1710

# the log where 1 func with wrapper calls another func with wrapper
calling test_function4_with_args(list_t * p_list = (list_t []){{.x = 42.420000, .p_next = (struct list_s []){{.x = 45.400000, .p_next = NULL,},},},})
calling test_function3_with_args(list_t * p_list = (list_t []){{.x = 42.420000, .p_next = (struct list_s []){{.x = 45.400000, .p_next = NULL,},},},})
test_function3_with_args(list_t * p_list = (list_t []){{.x = 42.420000, .p_next = (struct list_s []){{.x = 45.400000, .p_next = NULL,},},},}) returned 87.820000
test_function4_with_args(list_t * p_list = (list_t []){{.x = 42.420000, .p_next = (struct list_s []){{.x = 45.400000, .p_next = NULL,},},},}) returned -912.180000

Nampaknya Metac mencetak untuk kami gambaran yang mendalam tentang hujah serta keputusan. Secara umum ia berfungsi, walaupun terdapat beberapa kelemahan seperti keperluan untuk mengendalikan setiap saiz hujah secara berasingan.

Berikut adalah beberapa had tambahan:

  1. clang tidak mendedahkan maklumat nyahpepijat tentang fungsi luaran seperti printf. Ini bermakna - pembalut kami tidak akan berfungsi dengan apa adanya. Kami mungkin perlu memperkenalkan beberapa helah tambahan.
  2. berfungsi dengan hujah yang tidak ditentukan ... tidak akan menunjukkan hujah sedemikian. tiada cara generik, tetapi kami berkemungkinan ingin memberikan cara untuk menyediakan panggilan balik untuk mengekstrak maklumat bagi kes sedemikian.
  3. tiada (belum?) sokongan untuk kes hujah yang dikaitkan, mis. apabila kita memberikan penunjuk dan panjang sebagai 2 hujah yang berasingan tetapi bersambung secara logik .

Jika anda mempunyai sebarang cadangan tentang cara ia boleh menjadi lebih generik - sila komen. Terima kasih kerana membaca!

Atas ialah kandungan terperinci C Reflection Magic: Pembalakan Mudah dengan Pembungkus untuk Mencetak Argumen dan Keputusan Fungsi Arbitrari. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn