2017年9月19日 星期二

Zero-length arrays

Zero-length arrays are allowed in GNU C. 
They are very useful as the last element of a structure that is really a header for a variable-length object

在某些情況下,一個資料結構之定義的尾端會包含一個選用區塊
#include <stdio.h>
#include <stdlib.h>

struct abc {
    int age;
    char *name[20];
    //...
    //char *placeholder;
    char placeholder[0];
};

int main()
{
    char buffer[30];
    struct abc *data = malloc(sizeof(struct abc) + 100);
    //placeholder會指向後面多的那100

    printf("sizeof(struct abc) = %zu\n", sizeof(struct abc));
    printf("data = %p, placeholder = %p\n", data, data->placeholder);

    return 0;
}

$ ./a.out
sizeof(struct abc) = 168

data = 0x555c65741010, placeholder = 0x555c657410b8

選用區塊從placeholder起算
placeholder被定義成大小為0的向量。也就是說,abc被分配時若包含這個選用區塊,placeholder就會指向此區塊的開端處。不需要選用區塊時,placeholder就只是一個指向此結構尾端的指標;不會耗用任何空間

include/net/inet_sock.h
/** struct ip_options - IP Options
 *
 * @faddr - Saved first hop address
 * @nexthop - Saved nexthop address in LSRR and SSRR
 * @is_strictroute - Strict source route
 * @srr_is_hit - Packet destination addr was our one
 * @is_changed - IP checksum more not valid
 * @rr_needaddr - Need to record addr of outgoing dev
 * @ts_needtime - Need to record timestamp
 * @ts_needaddr - Need to record addr of outgoing dev
 */
struct ip_options {
    __be32      faddr;
    __be32      nexthop;
    unsigned char   optlen;
    unsigned char   srr;
    unsigned char   rr;
    unsigned char   ts;
    unsigned char   is_strictroute:1,
            srr_is_hit:1,
            is_changed:1,
            rr_needaddr:1,
            ts_needtime:1,
            ts_needaddr:1;
    unsigned char   router_alert;
    unsigned char   cipso;
    unsigned char   __pad2;
    unsigned char   __data[0];
};

net/ipv4/ip_sockglue.c
static int do_ip_getsockopt(struct sock *sk, int level, int optname,
                char __user *optval, int __user *optlen, unsigned int flags)
{
...
    switch (optname) {
    case IP_OPTIONS:
    {
        unsigned char optbuf[sizeof(struct ip_options)+40];
        struct ip_options *opt = (struct ip_options *)optbuf;
        struct ip_options_rcu *inet_opt;

        inet_opt = rcu_dereference_protected(inet->inet_opt,
                             lockdep_sock_is_held(sk));
        opt->optlen = 0;
        if (inet_opt)
            memcpy(optbuf, &inet_opt->opt,
                   sizeof(struct ip_options) +
                   inet_opt->opt.optlen);
        release_sock(sk);

        if (opt->optlen == 0)
            return put_user(0, optlen);

        ip_options_undo(opt);

        len = min_t(unsigned int, len, opt->optlen);
        if (put_user(len, optlen))
            return -EFAULT;
        if (copy_to_user(optval, opt->__data, len))
            return -EFAULT;
        return 0;

    }
...
}

net/ipv4/ip_output.c
static int ip_setup_cork(struct sock *sk, struct inet_cork *cork,
             struct ipcm_cookie *ipc, struct rtable **rtp)
{
...
    opt = ipc->opt;
    if (opt) {
        if (!cork->opt) {
            cork->opt = kmalloc(sizeof(struct ip_options) + 40,
                        sk->sk_allocation);
            if (unlikely(!cork->opt))
                return -ENOBUFS;
        }
        memcpy(cork->opt, &opt->opt, sizeof(struct ip_options) + opt->opt.optlen);
        cork->flags |= IPCORK_OPT;
        cork->addr = ipc->addr;
    }
...
}

note:
用在不確定後面會不會有payload的而且是不一定長度的時候
要小心資料對齊問題
主要是減少一次記憶體分配,釋放時也少一次,比較方便

參考資料
https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html

沒有留言:

張貼留言