C struct检查成员是否存在

如何判断C struct中是否存在某一成员,最开始想法是通过offsetof宏定义

#define offsetof(st, m) \
    ((size_t)&(((st *)0)->m))

C语言的offsetof()宏是ANSI C标准库中的一个特性,位于stddef.h头文件中。它用于计算给定结构体或联合体类型中某个成员的偏移量(以字节为单位), 但是这种方式仅能检查存在的成员, 不存在编译会有下面类似报错

XXX.c:11:35: error: ‘struct MyStruct’ has no member named ‘member3’; did you mean ‘member1’?

生成成员名的map

C/C++ 不支持反射,与例如 Java 或 C# 不同。这意味着在 C/C++ 中,你不能在运行时”检测”未知结构(或类)的成员,你必须在编译时知道它 – 通常是通过 #include 包含定义结构的头文件。实际上,你不能访问未定义结构的内容 – 只能传递指针。

from https://cplusplus.com/forum/beginner/283156/

同样作者给出了一个解决方案,那就是把对象放到map中, 对于C语言的,可以使用脚本处理一下。

大概思路根据脚本解析结构体,生成包含成员的数组,通过数组判断

# generate_struct_member_array.py

import re

def filter_content(content):
    # Remove comments
    content = re.sub(r'\/\*[\s\S]*?\*\/|\/\/.*', '', content)

    # Remove #define statements
    content = re.sub(r'#define\s+\w+\s+\w+', '', content)

    return content

def parse_header(header_content):
    struct_pattern = re.compile(r'struct\s+(\w+)\s*{([^}]*)};', re.DOTALL)
    structs = struct_pattern.findall(header_content)

    struct_member_arrays = []
    for struct_name, struct_body in structs:
        members = re.findall(r'\b\w+\s+(\w+)\s*(?:\[\w*\])?;', struct_body)
        struct_member_arrays.append((struct_name, members))

    return struct_member_arrays

def generate_array_code(struct_member_arrays):
    code = ""
    for struct_name, members in struct_member_arrays:
        code += f"const char *{struct_name}_member_names[] = {{\n"
        for member in members:
            code += f'\t"{member}",\n '
        code = code.rstrip(', ')
        code += "};\n"

    return code

def write_to_file(file_path, content):
    with open(file_path, 'w') as output_file:
        output_file.write(content)

def read_and_generate(input_file_path, output_file_path):
    # Read the input header file content
    with open(input_file_path, 'r') as header_file:
        header_content = header_file.read()

    # Filter content (remove comments and #define statements)
    filtered_content = filter_content(header_content)

    # Parse the header file
    struct_member_arrays = parse_header(filtered_content)

    # Generate code for arrays
    array_code = generate_array_code(struct_member_arrays)

    # Write generated code to a new file
    write_to_file(output_file_path, array_code)

    print(f"Generated code has been written to {output_file_path}")

# Specify the path to the input header file and the output header file
input_header_file_path = 'hello.h'
output_header_file_path = 'generated_define.h'

# Read the input header file, generate code, and write to the output header file
read_and_generate(input_header_file_path, output_header_file_path)

hello.h

struct MyStruct {
    int member1;
    int member2;
};

hello.c

#include "hello.h"
#include <stdio.h>
#include "generated_define.h"
#include <string.h>

int check_member(char *member)
{
    int i ;
    for (i=0; i<sizeof(MyStruct_member_names)/sizeof(MyStruct_member_names[0]); i++)
    {
        if(strcmp(member, MyStruct_member_names[i]) == 0) {
             return 1;
        }
    }
    return 0;

}
int main() {
    if (check_member("member1") == 1) {
        printf("has member1 yes \n");
    } else {
        printf("has member1 no \n");
    }
    if (check_member("member3") == 1) {
        printf("has member3 yes \n");
    } else {
        printf("has member3 no \n");
    }
    return 0;
}
garlic@garlic:~/io_uring$ python3 gen_new3.py
Generated code has been written to generated_define.h
garlic@garlic:~/io_uring$ gcc hello.c -o hello
garlic@garlic:~/io_uring$ ./hello
has member1 yes
has member3 no

使用宏定义

在学习nginx源码的时候, 看过nginx是通过自己的脚本自动生成一些宏定义个. 逻辑改为通过宏定义判断,

 

#include "config.h"

#include "hello.h"

#include <stdio.h>

int main() {
#ifdef HAS_MEMBER
    printf("has member3! yes \n");
#else
    printf("has member3! no\n");
#endif
    return 0;
}

Autoconf、automake和libtool是GNU Autotools家族,它们会生成安装脚本,可以用autoconf来实现

参考 https://www.idryman.org/blog/2016/03/10/autoconf-tutorial-1/

通过 AC_CHECK_MEMBER 增加对结构体成员的判断

# Must init the autoconf setup
# The first parameter is project name
# second is version number
# third is bug report address
AC_INIT([hello], [1.0])

# Safety checks in case user overwritten --srcdir
AC_CONFIG_SRCDIR([hello.c])

# Store the auxiliary build tools (e.g., install-sh, config.sub, config.guess)
# in this dir (build-aux)
AC_CONFIG_AUX_DIR([build-aux])

# Init automake, and specify this program use relaxed structures.
# i.e. this program doesn't follow the gnu coding standards, and doesn't have
# ChangeLog, COPYING, AUTHORS, INSTALL, README etc. files.
AM_INIT_AUTOMAKE([-Wall -Werror foreign])

# Check for C compiler
AC_PROG_CC
# We can add more checks in this section

# Check if the member exists in the struct
AC_CHECK_MEMBER([struct MyStruct.member3], [AC_DEFINE([HAS_MEMBER3],[1],[struct has member3])], [],[#include "hello.h"])
AC_CHECK_MEMBER([struct MyStruct.member1], [AC_DEFINE([HAS_MEMBER1],[1],[struct has member1])], [],[#include "hello.h"])


# Tells automake to create a Makefile
# See https://www.gnu.org/software/automake/manual/html_node/Requirements.html
#AC_CONFIG_HEADERS([config.h])
AC_CONFIG_FILES([Makefile])

# Generate the output
AC_OUTPUT

生成Makefile.am

# Makefile.am

bin_PROGRAMS = hello

hello_SOURCES = hello.c

 

garlic@garlic:~/io_uring/autoconf$ autoreconf --install --force
garlic@garlic:~/io_uring/autoconf$ make
....
gcc -DPACKAGE_NAME=\"hello\" -DPACKAGE_TARNAME=\"hello\" -DPACKAGE_VERSION=\"1.0\" -DPACKAGE_STRING=\"hello\ 1.0\" -DPACKAGE_BUGREPORT=\"\" -DPACKAGE_URL=\"\" -DPACKAGE=\"hello\" -DVERSION=\"1.0\" -DHAS_MEMBER1=1 -I.     -g -O2 -MT hello.o -MD -MP -MF .deps/hello.Tpo -c -o hello.o hello.c
mv -f .deps/hello.Tpo .deps/hello.Po
gcc  -g -O2   -o hello hello.o
garlic@garlic:~/io_uring/autoconf$ ./hello
has member3 no
has member1 yes

 

其他语言怎么做

之前帖子作者已经说过, 其实就是可以用反射机制, 处理看下golang

package main

import (
        "fmt"
        "reflect"
)

type MyStruct struct {
        member1 int
        member2 string
}

func hasmember(s interface{}, fieldName string) bool {
        st := reflect.TypeOf(s)
        for i := 0; i < st.NumField(); i++ {
                if st.Field(i).Name == fieldName {
                        return true
                }
        }
        return false
}

func main() {
        myStruct := MyStruct{member1: 42, member2: "Hello"}

        if hasmember(myStruct, "member1") {
                fmt.Println("member1 exists!")
        } else {
                fmt.Println("member1 does not exist!")
        }

        if hasmember(myStruct, "member3") {
                fmt.Println("member3 exists!")
        } else {
                fmt.Println("member3 does not exist!")
        }
}

python

class MyClass:
    def __init__(self):
        self.member1 = 42
        self.member2 = "Hello"

# 创建一个对象
my_object = MyClass()

# 检查对象是否具有指定属性
if hasattr(my_object, 'member1'):
    print("Object has 'member1' attribute!")
else:
    print("Object does not have 'member1' attribute!")

if hasattr(my_object, 'member3'):
    print("Object has 'member3' attribute!")
else:
    print("Object does not have 'member3' attribute!")

[garlic@dev python]$ python3 struct.py
Object has 'member1' attribute!
Object does not have 'member3' attribute!

 

 

反射机制的常见用途:

  1. 动态加载类和模块: 反射可以在运行时加载和实例化类,或者动态地加载模块。这使得程序能够根据运行时条件选择加载不同的实现。
  2. 查看和修改对象的属性和方法: 反射允许程序在运行时检查和修改对象的属性和方法。这对于实现通用的数据结构处理代码或进行对象序列化和反序列化很有用。
  3. 序列化和反序列化: 反射可用于将对象转换为字节流或其他格式,以便于存储或传输,并在需要时重新构建对象。JSON、XML 解析库通常使用反射来动态地将数据映射到对象属性。
  4. 注解和元数据处理: 反射使得程序能够读取和处理源代码中的注解或其他元数据。这在很多框架和库中都有广泛的应用,例如 Java 的注解处理器、Python 的装饰器等。
  5. 接口实现和类型判断: 反射允许程序在运行时检查对象是否实现了特定的接口,或者获取对象的实际类型。这对于实现插件系统或处理复杂的继承结构很有用。
  6. 调试和日志记录: 反射可以用于生成调试信息和日志,包括对象的类型、属性和方法。这有助于开发人员诊断和理解程序的运行时行为。

 

缘起

要将新分支一些新功能迁移到旧分支上,发现结构体变化了, 当时想偷懒就用offsetof, sizeof尝试判断了一下,后面就有了上面的笔记

 

参考及引用

https://www.geeksforgeeks.org/the-offsetof-macro/

https://en.wikipedia.org/wiki/Offsetof

https://cplusplus.com/forum/beginner/283156/

https://www.idryman.org/blog/2016/03/10/autoconf-tutorial-1/

https://gcc.gnu.org/onlinedocs/gcc/Offsetof.html

https://ftp.gnu.org/old-gnu/Manuals/autoconf-2.57/html_chapter/autoconf_5.html#SEC55

图片from 楊淳安

Comments are closed.