V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Distributions
Ubuntu
Fedora
CentOS
中文资源站
网易开源镜像站
vituralfuture
V2EX  ›  Linux

Linux 是如何隐藏`DIR`结构体定义的

  •  
  •   vituralfuture · 300 天前 · 2021 次点击
    这是一个创建于 300 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近在看 APUE 时遇到了DIR,提到了由于不同的实现,解析目录文件内容方式不统一,通常会阻止直接读取目录文件的内容

    DIR *opendir (const char *name)
    

    跳转到DIR的定义

    /* This is the data type of directory stream objects.
       The actual structure is opaque to users.  */
    typedef struct __dirstream DIR;
    

    __dirstream定义找不到
    注释说 DIR 结构体对用户透明,那么它是如何隐藏这个定义的呢?

    源文件

    #include <stdio.h>
    #include <dirent.h>
    
    int main() {
        DIR* dir = opendir(".");
        struct dirent *d = readdir(dir);
        while (d) {
            printf("%s\n", d->d_name);
            d = readdir(dir);
        }
        return 0;
    }
    

    使用 gcc 预处理源文件

    ➜  test gcc -E ls.c | grep __dirstream -A2 -B2
      };
    # 127 "/usr/include/dirent.h" 3 4
    typedef struct __dirstream DIR;
    
    
    

    并没有找到__dirstream的定义

    所以是如何实现隐藏定义的?

    6 条回复    2024-02-16 02:34:16 +08:00
    geelaw
        1
    geelaw  
       300 天前 via iPhone   ❤️ 2
    C 语言不要求所有 struct 都有定义,只要声明之后就可以使用指针。(当然用 sizeof 作用,或者定义该类型对象或数组,是需要该结构体的定义的。)所谓它是 opaque 就是说不提供定义。

    从 C 的 ABI 的角度,结构体指针和 void 指针没啥区别。实现 opendir 的人可能知道 DIR 的定义,并分配好内存、填充好数据返回给调用者。
    geelaw
        2
    geelaw  
       300 天前 via iPhone
    举个例子:

    // a.c
    #include<malloc>
    typedef struct a { int b; } a;
    a *foo(void)
    {
    return (a *)malloc(sizeof(a));
    }

    编译 a.c 之后得到 a.obj ,删去 a.c

    // b.c
    typedef struct a a;
    a *foo(void);
    int main(void) { foo(); }

    编译 b.c 并和 a.obj 链接。结果是 b 可以正常执行,在 b 产生的时候不需要 a.c 的存在。

    现在的状况就是 opendir 在别人写的 a.c 里面,但别人没有提供 a.c 而是提供了 a.obj ,而别人提供的 .h 是上面 b.c 的前两行。
    yanqiyu
        3
    yanqiyu  
       300 天前
    因为除了 libc 内部之外不需要接触到 DIR 这个结构的成员

    结构体的定义就是告诉编译器,结构体的成员排布(每个成员的偏移,结构体的大小),要是编译器用不到这些信息就不会要求必须看到定义。( C++ 管这叫做 odr-use ,但是不知道 C 有没有类似的术语,也可以类比前向声明的时候不需要具体定义)

    要是没有用到具体定义的翻译单元,就没必要让编译器看到结构体的定义。然后包含这个结构体的定义的头文件大概没有被发布出来,只是被一些内部函数的定义的代码用到了(就是编译 libc 的时候有,但是在 libc 安装的时候没有被拷贝出来)。
    vituralfuture
        4
    vituralfuture  
    OP
       300 天前   ❤️ 1
    理解了,这个`DIR`就是一个不完整的类型,因为只有声明,能通过编译,只能当作指针使用,而它的成员、大小都是未知的

    如果对`DIR`使用`sizeof`或者指针运算,就无法通过编译
    lance6716
        5
    lance6716  
       300 天前 via Android
    原来更高级语言的抽象啊接口啊,在 C 里是这样的。学习了
    fpk5
        6
    fpk5  
       300 天前
    library 一般通过提供结构的 declaration 而没有 definition 的方式来隐藏内部实现的(类比 private 成员)。对于内部成员的操作需要全部通过函数暴露出来,函数没有提供的操作就是 private 的方便库作者后面更改。`__dirstream` 在 linux 里的 definition 在 glibc 里 https://elixir.bootlin.com/glibc/latest/source/sysdeps/unix/sysv/linux/dirstream.h#L30
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3116 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 04:48 · PVG 12:48 · LAX 20:48 · JFK 23:48
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.