Skip to content
Published at:

结构体还是结构体指针?

缘由:

上周某日晚上的一个项目进度跟进会上,项目中有一个涉及到一个比较大的结构体,然后内部嵌套了好些结构体。后来我问了句:“为啥使用的是结构体,而不是结构体指针?” 同事答到:“都一样”。后来,讨论了一些“如果你嵌套的那个结构体非常大的话,而这个结构体在使用过程中没有被初使化用到,这样会很浪费内存,这种情况使用结构体指针会比较合适”。

由于当时会议的重点不在这里,草草过了。后面越想越不对,这应该是一个值得思考的问题,嵌套时:是使用结构体还是结构体指针,应该是个哲学问题~~~。不过可以从一些角度来分析这个问题

不同角度分析:

  • 从内存使用上
  • 抽象的关系上
  • 嵌套和继承/组合的关系
  • 参考优秀的代码

从内存使用上

正如前因提到的:

  • 如果这个被嵌套的结构体字段很多;比如200个字段
  • 而这个被嵌套的结构体并不是很个场景都会用的上

这时候用结构体指针可以省不少的空间,就很有意义;那就为了省内存而全使用结构体指针?

抽象的关系上

编程本质上也是给问题建模,抽象问题,代码也用来描述这些抽象之间的关系的,所以就有了下面的区别:

  • 嵌套的是结构体:有种“包含”或“属于”的关系。内部的结构体“属于”这个外部的在大结构体,内部是外部结构体的组成部分,是它的一员。比如,一个人属于这个部门的人;在比如,协议数据报中,嵌套的关系,一个结构体属于一个结构体
  • 嵌套的是结构体指针:有种引用的关系。内部的结构体不属于你,但会“用到”你。比如,为了完成一个项目,这时候需要由不同的部门抽调人过来组成一个项目团队(结构体),这个团队会“用到(引用)”其它部门的人,我在某一时期,某个阶段可能会用到你,也可能不会用到你(的功能)

结构体嵌套和继承/组合的关系

后来突然冒出一个问题:类的继承如何在C语言中来实现?大概就是用嵌套结构体吧

业内有名名言:“组合优于继承”,这时候:

  • 继承就是结构体嵌套结构体。(子类包含父类中的所有成员)
  • 而组合就是结构体嵌套结构体指针。(因为里面的对象都要new,而new是在heap上,所以是指针、引用)

所以根据这句名言的结论就是:优先使用嵌套结构体指针

参考优秀的代码

有时候不知道怎么做合适,可以去参考下优秀的代码,归纳总结下,看看别人是怎么做的,参考参考(抄一抄);下面linux源码,进程的结构体表示

c
struct task_struct {
/* these are hardcoded - don't touch */
    volatile long state;	/* -1 unrunnable, 0 runnable, >0 stopped */
    long counter;
    long priority;
    unsigned long signal;
    unsigned long blocked;	/* bitmap of masked signals */
    unsigned long flags;	/* per process flags, defined below */
    int errno;
    long debugreg[8];  /* Hardware debugging registers */
    struct exec_domain *exec_domain;
/* various fields */
    struct linux_binfmt *binfmt;
    struct task_struct *next_task, *prev_task;
    struct task_struct *next_run,  *prev_run;
    unsigned long saved_kernel_stack;
    unsigned long kernel_stack_page;
    int exit_code, exit_signal;
    /* ??? */
    unsigned long personality;
    int dumpable:1;
    int did_exec:1;
    /* shouldn't this be pid_t? */
    int pid;
    int pgrp;
    int tty_old_pgrp;
    int session;
    /* boolean value for session group leader */
    int leader;
    int	groups[NGROUPS];
    /*
     * pointers to (original) parent process, youngest child, younger sibling,
     * older sibling, respectively.  (p->father can be replaced with
     * p->p_pptr->pid)
     */
    struct task_struct *p_opptr, *p_pptr, *p_cptr, *p_ysptr, *p_osptr;
    struct wait_queue *wait_chldexit;	/* for wait4() */
    unsigned short uid,euid,suid,fsuid;
    unsigned short gid,egid,sgid,fsgid;
    unsigned long timeout, policy, rt_priority;
    unsigned long it_real_value, it_prof_value, it_virt_value;
    unsigned long it_real_incr, it_prof_incr, it_virt_incr;
    struct timer_list real_timer;
    long utime, stime, cutime, cstime, start_time;
/* mm fault and swap info: this can arguably be seen as either mm-specific or thread-specific */
    unsigned long min_flt, maj_flt, nswap, cmin_flt, cmaj_flt, cnswap;
    int swappable:1;
    unsigned long swap_address;
    unsigned long old_maj_flt;	/* old value of maj_flt */
    unsigned long dec_flt;		/* page fault count of the last time */
    unsigned long swap_cnt;		/* number of pages to swap on next pass */
/* limits */
    struct rlimit rlim[RLIM_NLIMITS];
    unsigned short used_math;
    char comm[16];
/* file system info */
    int link_count;
    struct tty_struct *tty; /* NULL if no tty */
/* ipc stuff */
    struct sem_undo *semundo;
    struct sem_queue *semsleeping;
/* ldt for this task - used by Wine.  If NULL, default_ldt is used */
    struct desc_struct *ldt;
/* tss for this task */
    struct thread_struct tss;
/* filesystem information */
    struct fs_struct *fs;
/* open file information */
    struct files_struct *files;
/* memory management info */
    struct mm_struct *mm;
/* signal handlers */
    struct signal_struct *sig;
#ifdef __SMP__
    int processor;
    int last_processor;
    int lock_depth;		/* Lock depth. We can context switch in and out of holding a syscall kernel lock... */
#endif
};

其中除了struct thread_struct tssstruct rlimit rlim[RLIM_NLIMITS]用的结构体,一个是线程,一个是进程资源大小的限制,更像是在表达这俩就“属于”进程,必不可少。其它的ipc,fs,memory management,signal handlers,更像是为了组合到一起来完成任务,会“用到”你们(的功能),它们各自有独立的功能。

总结

好像结论已经很明显了,更加倾向于从抽象的角度看这个问题,如果是有“包含”或“属于”的关系在里面,就用嵌套结构体;而如果是有“组合”到一起完成某种功能,它承担了一些独特的任务功能,这时可以用嵌套结构体指针

Updated at: