有没有一种方法可以转储C结构体?

9

我编写了一个程序来探查系统的C时间.h函数的极限,并以JSON格式输出它们。那么依赖这些函数的其他东西就可以知道它们的限制。

# system time.h limits, as JSON 
{
  "gmtime": {
    "max": 2147483647,
    "min": -2147483648
  },
  "localtime": {
    "max": 2147483647,
    "min": -2147483648
  },
  "mktime": {
    "max": {
      "tm_sec": 7,
      "tm_min": 14,
      "tm_hour": 19,
      "tm_mday": 18,
      "tm_mon": 0,
      "tm_year": 138,
      "tm_wday": 1,
      "tm_yday": 17,
      "tm_isdst": 0
    },
    "min": {
      "tm_sec": 52,
      "tm_min": 45,
      "tm_hour": 12,
      "tm_mday": 13,
      "tm_mon": 11,
      "tm_year": 1,
      "tm_wday": 5,
      "tm_yday": 346,
      "tm_isdst": 0
    }
  }
}

gmtime()和localtime()很简单,它们只需要数字,但是mktime()需要一个tm结构体。我编写了一个自定义函数来将tm struct转换为JSON哈希表。

/* Dump a tm struct as a json fragment */
char * tm_as_json(const struct tm* date) {
    char *date_json = malloc(sizeof(char) * 512);
#ifdef HAS_TM_TM_ZONE
    char zone_json[32];
#endif
#ifdef HAS_TM_TM_GMTOFF
    char gmtoff_json[32];
#endif

    sprintf(date_json,
            "\"tm_sec\": %d, \"tm_min\": %d, \"tm_hour\": %d, \"tm_mday\": %d, \"tm_mon\": %d, \"tm_year\": %d, \"tm_wday\": %d, \"tm_yday\": %d, \"tm_isdst\": %d",
            date->tm_sec, date->tm_min, date->tm_hour, date->tm_mday,
            date->tm_mon, date->tm_year, date->tm_wday, date->tm_yday, date->tm_isdst
    );

#ifdef HAS_TM_TM_ZONE
    sprintf(&zone_json, ", \"tm_zone\": %s", date->tm_zone);
    strcat(date_json, zone_json);
#endif
#ifdef HAS_TM_TM_GMTOFF
    sprintf(&gmtoff_json", \"tm_gmtoff\": %ld", date->tm_gmtoff);
    strcat(date_json, gmtoff_json);
#endif

    return date_json;
}

有没有一种通用的方法,可以针对任何给定的结构体进行操作?
注意:这是C语言,不是C++。
6个回答

4

一般情况下,不适用于C语言。但是如果使用调试符号编译C模块,并且目标模块可用,则可以解析它并发现有关结构的所有信息。我敢打赌你的系统中有一个库可以帮助你。


3
我遇到了同样的问题,于是我自己写了一个库:https://github.com/jamie-pate/jstruct。它被编写成允许对现有C结构进行注释,然后根据注释生成元数据信息。该库读取元数据来将C结构导入/导出为JSON字符串。Python脚本负责解析已注释的头文件并生成新的头文件和元数据初始化器。jstruct库在内部使用https://github.com/json-c/json-c
我还注意到了https://github.com/marel-keytech…但那是在我全部完成后才发现的。(并且该项目页面上的信息很少)
目前还没有支持从现有系统库中注释单个结构的功能,但您可以在已注释的自定义结构中包装tm结构。(我想?)
如果您有使该库更实用的想法,可以添加功能请求或甚至提交拉动请求。我的一个想法是添加一种嵌入此类只读结构的方法,即在具有@inline注释的注释包装器内,例如(目前不支持但可以添加)
//@json
struct my_time {
    //@inline
    struct tm tm;
}

代码:

struct my_time t;

mktime(&t.tm);
struct json_object *result = jstruct_export(t, my_time);

同时,您可以不使用@inline(因为它尚未编写),只需手动提取tm属性,并使用json_object_to_json_string(json_object_object_get(result, "tm"))进行提取。


2
这不完全符合您的要求,但可能会有所帮助:

这并不能完全满足您的需求,但可能会有一些帮助:

#define NAME_AND_INT(buf, obj, param) \
        sprintf((buf), "\"%s\": %d, ", #param, (obj)->(param))

接下来你可以进行迭代,例如(注意:未经测试;请将其视为伪代码):

char * tm_as_json(const struct tm* date) {
    /* ... */
    char buf[BUFSIZ]; /* or, use your date_json */

    pos = buf; /* I note you use the equivalent of &buf -- that works too */
               /* (not sure which is "better", but I've always left the & off
                * things like that -- buf is essentially a pointer, it's just
                * been allocated in a different way.  At least that's how I
                * think of it. */
    pos += NAME_AND_INT(pos, date, tm_sec);
    pos += NAME_AND_INT(pos, date, tm_min);
    /* ... more like this ... */

    /* strip trailing ", " (comma-space): */
    pos-=2;
    *pos = '\0';

    /* ... */
}

你可以类似地定义NAME_AND_STRINGNAME_AND_LONG等(对于tm_zone和tm_gmtoff),根据需要进行调整。
再次强调,这不是一个通用的解决方案,但至少能让你更接近目标。

好的,我很高兴它对你有帮助。另外,我刚注意到在宏展开时,“buf”周围应该加上括号。我已经编辑了我的答案以添加括号。 - lindes

1

免责声明:我是https://github.com/tamask1s/zax-parser项目的所有者。

使用该库,您可以将C结构转换为JSON,只需提供有关需要转换的结构成员的一些信息即可。您无需在项目中包含生成的代码,但需要使用c++11编译器才能使用它。

该库还不够成熟,因为我仅实现了我所需的功能,但您可以扩展它,或将其用作灵感。

示例:

#define some_json_properties JSON_PROPERTY(x), JSON_PROPERTY(s) 
 
struct some_class 
{ 
    int x = 9; 
    std::string s = "something";  
 
    ZAX_JSON_SERIALIZABLE(some_class, some_json_properties) 
}; 
 
std::string some_json = some_obj; 

"

---some_json 的值为:---

"
{"x":9, "s":"something"} 

嵌套对象也是可能的,请查看此示例:https://tamask1s.github.io/zax-parser/index.html#Parsing_of_structures_with_fields_of_serializable_structures

Gert Arnold谢谢!我刚刚用“由我开发”扩展了第一句话。那就够了吗?还是我应该做些别的事情?我进行了快速搜索,但是没有找到有关这个的任何信息,所以现在有点不知所措... - tamask1s
谢谢你的帮助! - tamask1s

0

这个宏并不能完全满足你的需求(生成C数据的JSON转储),但我认为它展示了一些可能性。你可以通过“p(...);”调用来转储任何C数据的内容。

我使用gdb作为外部帮助程序来使其工作,但是也可以使用libbfd实现一个。在这种情况下,你可以完全控制输出 - 比如生成JSON兼容的输出。

#ifndef PP_H
#define PP_H
/*
* Helper function (macro) for people who loves printf-debugging.
* This dumps content of any C data/structure/expression without prior
* knowledge of actual format. Works just like "p" or "pp" in Ruby.
*
* Usage:
* p(anyexpr);
*
* NOTE:
* - Program should be compiled with "-g" and preferrably, with "-O0".
*
* FIXME:
* - Would be better if this doesn't depend on external debugger to run.
* - Needs improvement on a way prevent variable from being optimized away.
*/

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

// Counts number of actual arguments.
#define COUNT_(_1, _2, _3, _4, _5, _6, _7, _8, N, ...) N
#define COUNT(...) COUNT_(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1)

// Dispatches macro call by number of actual arguments.
// Following is an example of actual macro expansion performed in case
// of 3 arguments:
//
// p(a, b, c)
// -> FUNC_N(p, COUNT(a, b, c), a, b, c)
// -> FUNC_N(p, 3, a, b, c)
// -> p_3(a, b, c)
//
// This means calling with simple "p(...)" is fine for any number of
// arguments, simulating "true" variadic macro.
#define CONCAT(name, count) name##count
#define FUNC_N(name, count, ...) CONCAT(name, count)(__VA_ARGS__)

// Forbids variable from being optimized out, so debugger can access it.
//
// FIXME:
// - Current implementation does not work with certain type of symbols
#define ENSURE(...) FUNC_N(ENSURE_, COUNT(__VA_ARGS__), __VA_ARGS__)
#define ENSURE_1(a) asm(""::"m"(a))
#define ENSURE_2(a, ...) do { ENSURE_1(a); ENSURE_1(__VA_ARGS__); } while (0)
#define ENSURE_3(a, ...) do { ENSURE_1(a); ENSURE_2(__VA_ARGS__); } while (0)
#define ENSURE_4(a, ...) do { ENSURE_1(a); ENSURE_3(__VA_ARGS__); } while (0)
#define ENSURE_5(a, ...) do { ENSURE_1(a); ENSURE_4(__VA_ARGS__); } while (0)
#define ENSURE_6(a, ...) do { ENSURE_1(a); ENSURE_5(__VA_ARGS__); } while (0)
#define ENSURE_7(a, ...) do { ENSURE_1(a); ENSURE_6(__VA_ARGS__); } while (0)
#define ENSURE_8(a, ...) do { ENSURE_1(a); ENSURE_7(__VA_ARGS__); } while (0)

// Dumps content of given symbol (uses external GDB for now)
//
// NOTE:
// - Should use libbfd instead of gdb? (but this adds complexity...)
#define PP_D(...) do { \
char *arg[] = { __VA_ARGS__, NULL }; \
char **argp = arg; \
char cmd[1024]; \
FILE *tmp = tmpfile(); \
fprintf(tmp, "attach %d\n", getpid()); \
fprintf(tmp, "frame 2\n"); \
while (*argp) \
fprintf(tmp, "p %s\n", *argp++); \
fprintf(tmp, "detach\n"); \
fflush(tmp); \
sprintf(cmd, "gdb -batch -x /proc/%d/fd/%d", \
getpid(), fileno(tmp)); \
system(cmd); \
fclose(tmp); \
} while (0)

#define PP(...) do { \
FUNC_N(PP_, COUNT(__VA_ARGS__), __VA_ARGS__); \
ENSURE(__VA_ARGS__); \
} while (0)
#define PP_1(a) do { PP_D(#a); } while (0)
#define PP_2(a,b) do { PP_D(#a,#b); } while (0)
#define PP_3(a,b,c) do { PP_D(#a,#b,#c); } while (0)
#define PP_4(a,b,c,d) do { PP_D(#a,#b,#c,#d); } while (0)
#define PP_5(a,b,c,d,e) do { PP_D(#a,#b,#c,#d,#e); } while (0)
#define PP_6(a,b,c,d,e,f) do { PP_D(#a,#b,#c,#d,#e,#f); } while (0)
#define PP_7(a,b,c,d,e,f,g) do { PP_D(#a,#b,#c,#d,#e,#f,#g); } while (0)
#define PP_8(a,b,c,d,e,f,g,h) do { PP_D(#a,#b,#c,#d,#e,#f,#g,#h); } while (0)

// Comment this out if you think this is too aggressive.
#define p PP

#endif

上面的粘贴中缩进丢失了,但您可以从以下链接获取源代码:https://github.com/tai/ruby-p-for-c


感谢您的回答。在我的情况下,依赖于非标准库,尤其是外部程序是不可能的。虽然我很想看看libbfd版本会是什么样子。 - Schwern

0

谢谢,但那是用 Perl 解析 C 结构体吗? - Schwern
仅使用Perl解析C结构几乎是不可能的,除非您在Perl中重写编译器的某些部分。而且这也不是必要的,因为您的编译器可能能够为此编译器创建符号信息:stabs、dwarf、xml。对于xml,有GCC::TranslationUnit,还有使用ucpp的Convert::Binary::C。 - rurban

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接