”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > C Reflection Magic:使用包装器进行简单记录,用于打印任意函数参数和结果

C Reflection Magic:使用包装器进行简单记录,用于打印任意函数参数和结果

发布于2024-08-16
浏览:418

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

This article is a research report which covers some potential implementation aspects of writing a helper wrapper which will automatically log arguments and results of the arbitrary C function. This is one of the examples why reflection may be useful even in C. The implementation is based on the Metac project. The introduction of it was given in this article. The research has some good results, but it still in progress. The comments on how it could be done in a better way are appreciated.

Logging is one of the important ways of debugging. Making proper logging is a key to understanding what potentially went wrong without using a debugger. But it’s annoying to print out all the arguments of each function and its result. C reflection with Metac could potentially have an ability to do this, because debugging information provided by DWARF has all the data about the type of each argument. Check it out. Here is the testing application:

#include 
#include 
#include 
#include 

#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;
}

We want to make some kind of wrapper to print arguments of test_function1_with_args. Metac will generate its reflection info since METAC_GSYM_LINK(test_function1_with_args); is in the code. For simplicity int and short argument types are selected. The first idea how we could create a wrapper is - create a macro:

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;
}

This wrapper so far handles only arguments, but it’s ok for the first step. Lets try to implement print_args. Here is the first naive attempt:

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  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;
}

If we run it we will see:

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

It works! But it handles only base types. And we want it to be universal.

The main challenge here is with this line:

 _type_ val = va_arg(args, _type_); 

C's va_arg macro requires the type of the argument to be known at compile time. However, reflection information only provides type names at runtime. Can we trick it? va_arg is a macros which covers a builtin function. The second parameter is a type (very non-typical thing). But why does this thing at all needs the type? The answer is - to understand the size and to be able to take it from the stack. We need to cover all possible sizes and to get a pointer to the next argument. On Metac side we know the size of argument - we can use this snippet to get it:

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

As a next idea let's make the macro which will cover 1 size and make sure that we handle it properly:

        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_

With this approach we covered different sizes from 1 to 32. We could generate a code and cover arguments sized till any arbitrary number, but in most cases people use pointers rather than passing arrays/structures directly. For the sake of our example we’ll keep 32.
Lets refactor our function to make it more reusable split it into 2 vprint_args and print_args similarly to ‘vprtintf’ and 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  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;
}

The reader may notice that we added p_tag_map as the first argument. This is for the further research - it's not used in this article.

Lets now try to create a part which handles the result. Unfortunately typeof isn’t supported till C23 (gcc extension as an option, but it won't work with clang) and we have a dilemma - do we want to keep our METAC_WRAP_FN notation as is, or it’s ok to pass it one more argument - type of the function result to be used as a buffer. Probably we could use libffi to handle this in a universal way - Metac knows the type, but it’s not clear how to put the returned data into the buffer of the proper size. For simplicity let’s change our macro:

#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; \
    })

Now we’re passing _type_ as a first argument to store the result. If we pass incorrect type or arguments - the compiler will complain about this _type_ res = _fn_(_args_). This is good.
Printing out the result is a trivial task, we already did that in the first article. Let’s also update our test functions to accept some different types of parameters.
Here is the final example code.

If we run it we’ll get with the comments:

% ./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

It’s seen that Metac prints for us the deep representation of the arguments as well as results. In general it works, though there are some flaws like a need to handle each size of argument separately.

Here are some additional limitations:

  1. clang doesn't expose debug information about external functions like printf. That means - our wrapper won't work with that as-is. We may need to introduce some additional tricks.
  2. functions with unspecified arguments ... won't show such arguments. there is no generic way, but potentially we may want to give a way to provide a callback to extract information for such cases.
  3. there is no (yet?) support for the cases of linked arguments, e.g. when we pass pointer and length as 2 separate but logically connected arguments .

If you have any suggestion on how it could be more generic - please comment. Thanks for reading!

版本声明 本文转载于:https://dev.to/alexey_odinokov_734a1ba32/c-reflection-magic-a-wrapper-for-printing-arbitrary-functions-arguments-and-results-1k0b如有侵犯,请联系[email protected]删除
最新教程 更多>
  • 如何在 PHP 中使用数组函数向左旋转数组元素?
    如何在 PHP 中使用数组函数向左旋转数组元素?
    在 PHP 中向左旋转数组元素在 PHP 中旋转数组,将第一个元素移动到最后一个元素并重新索引数组,可以使用 PHP 的 array_push() 和 array_shift() 函数组合来实现。PHP 函数:PHP 没有专门用于旋转的内置函数数组。但是,以下代码片段演示了如何模拟所需的旋转行为:$...
    编程 发布于2024-11-06
  • 如何解决Java访问文件时出现“系统找不到指定的路径”错误?
    如何解决Java访问文件时出现“系统找不到指定的路径”错误?
    解决 Java 中遇到“系统找不到指定的路径”时的文件路径问题在 Java 项目中,尝试访问文本时遇到错误来自指定相对路径的文件。此错误是由于 java.io.File 类无法定位指定路径而产生的。要解决此问题,建议从类路径中检索文件,而不是依赖文件系统。通过这样做,您可以消除相对路径的需要,并确保...
    编程 发布于2024-11-06
  • Laravel 中的 defer() 函数如何工作?
    Laravel 中的 defer() 函数如何工作?
    Taylor Otwell 最近宣布了 Laravel 中的新函数 defer()。这只是对 defer() 函数如何工作以及使用它可能遇到的问题进行非常基本的概述。 找出问题 还记得您曾经需要从 API 获取某些内容,然后在幕后执行一些用户不关心但仍在等待的操作的路由吗?是的,我们都至少经历过一次...
    编程 发布于2024-11-06
  • 在 Python Notebook 中探索使用 PySpark、Pandas、DuckDB、Polars 和 DataFusion 的数据操作
    在 Python Notebook 中探索使用 PySpark、Pandas、DuckDB、Polars 和 DataFusion 的数据操作
    Apache Iceberg Crash Course: What is a Data Lakehouse and a Table Format? Free Copy of Apache Iceberg the Definitive Guide Free Apache Iceberg Crash ...
    编程 发布于2024-11-06
  • Vue + Tailwind 和动态类
    Vue + Tailwind 和动态类
    我最近在做的一个项目使用了Vite、Vue和Tailwind。 使用自定义颜色一段时间后,我遇到了一些困惑。 在模板中添加和使用自定义颜色不是问题 - 使用 Tailwind 文档使该过程非常清晰 // tailwind.config.js module.exports = { them...
    编程 发布于2024-11-06
  • 端到端(E 测试:综合指南
    端到端(E 测试:综合指南
    端到端测试简介 端到端(E2E)测试是软件开发生命周期的重要组成部分,确保整个应用程序流程从开始到结束都按预期运行。与专注于单个组件或几个模块之间交互的单元或集成测试不同,端到端测试从用户的角度验证整个系统。这种方法有助于识别应用程序不同部分交互时可能出现的任何问题,确保无缝且无错误的用户体验。 ...
    编程 发布于2024-11-06
  • 可以在 Go 结构标签中使用变量吗?
    可以在 Go 结构标签中使用变量吗?
    在 Go 结构体标签中嵌入变量Go 的结构体标签通常用于注释和元数据,通常涉及简单的字符串文字。但是,用户可能会遇到在这些标签中需要动态或计算值的情况。考虑以下结构,其中带有为 JSON 封送注释的“类型”字段:type Shape struct { Type string `json:&q...
    编程 发布于2024-11-06
  • 如何增强 Visual Studio 的构建详细程度以实现深入洞察?
    如何增强 Visual Studio 的构建详细程度以实现深入洞察?
    熟悉 Visual Studio 的构建详细程度需要全面了解 Visual Studio 构建过程背后的复杂细节?别再犹豫了!虽然使用 vcbuild 不会产生所需的详细输出,但 Visual Studio 的设置中隐藏着一个解决方案。采取以下简单步骤即可解锁大量信息:导航至 Visual Stud...
    编程 发布于2024-11-06
  • 开发者日记# 谁写的?
    开发者日记# 谁写的?
    有一个想法困扰着我。也许,我们无法识别它,但日复一日,我们周围越来越多的人工智能生成的内容。 LinkedIn 或其他平台上的有趣图片、视频或帖子。我对帖子的媒体内容没有疑问(很容易识别它何时生成、从库存中获取或创建),但我对帖子的内容表示怀疑。几乎每次我读一篇文章时,我都会想这是谁写的?是作者分享...
    编程 发布于2024-11-06
  • 哪种方法计算数据库行数更快:PDO::rowCount 或 COUNT(*)?为什么?
    哪种方法计算数据库行数更快:PDO::rowCount 或 COUNT(*)?为什么?
    PDO::rowCount 与 COUNT(*) 性能在数据库查询中计算行数时,选择使用 PDO:: rowCount 和 COUNT(*) 会显着影响性能。PDO::rowCountPDO::rowCount 返回受最后一个 SQL 语句影响的行数。但是,对于 SELECT 语句,某些数据库可能会...
    编程 发布于2024-11-06
  • PART# 使用 HTTP 进行大型数据集的高效文件传输系统
    PART# 使用 HTTP 进行大型数据集的高效文件传输系统
    让我们分解提供的HTML、PHP、JavaScript和CSS代码对于分块文件上传仪表板部分。 HTML 代码: 结构概述: Bootstrap for Layout:代码使用 Bootstrap 4.5.2 创建一个包含两个主要部分的响应式布局: 分块上传部分:用于...
    编程 发布于2024-11-06
  • 比较:Lithe 与其他 PHP 框架
    比较:Lithe 与其他 PHP 框架
    如果您正在为下一个项目探索 PHP 框架,很自然会遇到 Laravel、Symfony 和 Slim 等选项。但是,是什么让 Lithe 与这些更强大、更知名的框架区分开来呢?以下是一些突出 Lithe 如何脱颖而出的注意事项。 1. 轻量级和性能 Lithe 的设计重点关注轻量级架...
    编程 发布于2024-11-06
  • 编码风格指南:编写简洁代码的实用指南
    编码风格指南:编写简洁代码的实用指南
    在过去的五年里,我一直在不断尝试提高我的编码技能,其中之一就是学习和遵循最推荐的编码风格。 本指南旨在帮助您编写一致且优雅的代码,并包含一些提高代码可读性和可维护性的建议。它的灵感来自于社区中最受接受的流行指南,但进行了一些修改以更适合我的喜好。 值得一提的是,我是一名全栈 JavaScript 开...
    编程 发布于2024-11-06
  • 检查类型是否满足 Go 中的接口
    检查类型是否满足 Go 中的接口
    在Go中,开发人员经常使用接口来定义预期的行为,使代码灵活且健壮。但是如何确保类型真正实现接口,尤其是在大型代码库中? Go 提供了一种简单有效的方法来在编译时验证这一点,防止运行时错误的风险并使您的代码更加可靠和可读。 您可能见过类似的语法 var _ InterfaceName = TypeN...
    编程 发布于2024-11-06
  • 掌握 JavaScript 中的 &#this&# 关键字
    掌握 JavaScript 中的 &#this&# 关键字
    JavaScript 中的 this 关键字如果不理解的话可能会非常棘手。这是即使是经验丰富的开发人员也很难轻松掌握的事情之一,但一旦你掌握了,它可以为你节省大量时间。 在本文中,我们将了解它是什么、它在不同情况下如何工作以及使用它时不应陷入的常见错误。 在 JavaScript ...
    编程 发布于2024-11-06

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3