/usr/src/linux-source-3.8.0/drivers/gpu/drm/radeon 这个文件夹以下

去找到这个文件 mkregtable.c  打开,就能够看到了。

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
/**
* container_of - cast a member of a structure out to the containing structure
* @ptr: the pointer to the member.
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
*
*/
#define container_of(ptr, type, member) ({ \
const typeof(((type *)0)->member)*__mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member)); })

问题的引发事实上是看linux kernel development的时候看kernel data structure,看到的有个container_of 用来计算结构体里面元素的偏移量。

问题重述:

typeof(((type *)0)->member)

这行代码为什么不会报错? 尝试把0 强制类型转换成指向type变量类型的指针,进而解引用这个指针指向结构体内部的member成员。

尝试去解引用指向地址0x00极其附近的指针都是非法的。

可是为什么会有这样的

((type *)0)->member

使用方法呢?

问题的关键是我没有深刻的理解这个 -> 解引用操作符

以下慢慢分析我们遇到的问题,适当的做一点知识的铺垫。。。

#include <stddef.h>
#include "stdio.h" int main()
{ struct foo
{
int a;
int b;
}temp; printf("temp:%d temp.a %d temp.b %d\n",&temp,&temp.a,&temp.b);
return 0;
}

jasonleaster@ubuntu:~$ ./a.out

temp:1233957888 temp.a 1233957888 temp.b 1233957892

上面这个demo就回想一下结构体变量本身的地址和结构体成员的地址之间的关系

不难看出。结构体第一个元素的地址就是结构体的地址, 第一个元素地址加上第一个元素的大小作为偏移量。得到第二个元素的地址。

继续铺垫一下。

所谓的左值和右值就是left value, righit value.

简单的说就是赋值符号= 左边的是左值。右边的是右值。当然有特殊情况。

这个概念的出现主要是为了说明变量名和地址的联系

assumption:

int a;
int b;//这是个坏习惯,变量没有初始化。 。。 为了说明问题。重点不在这里。 //以下是重点:
a = 10;
b = a;

对于a = 10。

是把10这个常量写入到a 标记的地址中去。变量名a此时是地址

而b = a;是把a的值赋值给b。注意,是将a地址中存储的值,写入到b标记的地址其中。这里有个动作须要注意。就是a作为右值,先是获知a标记的地址,然后去a标记的地址里面依照变量的类型。读数据!有个读的操作。

而当a = 10;时。是没有这个对于a读数据的操作的。

这样viewer应该可以感受到左值和右值的不同。

上面这两个铺垫对于我们遇到的问题的理解十分关键。

#include <stdio.h>

int main()
{
struct foo
{
int a;
int b;
int c;
int d;
}; (((struct foo*)0)->a);
(((struct foo*)0)->b);
return 0;
}

上面这个demo编译执行,全然没问题。

可是改动一下:

<span style="font-size:14px;">#include <stdio.h>

int main()
{
struct foo
{
int a;
int b;
int c;
int d;
}; (((struct foo*)0)->a);
(((struct foo*)0)->b); printf("%d",(((struct foo*)0)->c)); return 0;
}</span>

jasonleaster@ubuntu:~/Desktop$ ./a.out

Segmentation fault (core dumped)

呵呵。。。

挂的妥妥的。。。问题。。。事实上非常easy

这里printf要把这个数据打印出去,必然去读那个地址的数据。

上帝啊,去读0x00附近的数据(此处确切的说是0x08)那是找死。。

不挂才怪

(((struct foo*)0)->c)

被作为右值使用

(((struct foo*)0)->a);

而这里是没有赋值的,而且不会尝试去读取这个变量名标记的地址处的数据。

于是不会有问题。

上面没有printf的那个demo所以就一切OK

我们再返回那个内核代码片段看看

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
/**
* container_of - cast a member of a structure out to the containing structure
* @ptr: the pointer to the member.
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
*
*/
#define container_of(ptr, type, member) ({ \
const typeof(((type *)0)->member)*__mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member)); })

typeof是gnu 对C扩展的keyword。值得注意的是这家伙c99里面都没有。typeof依据变量名能够返回变量的类型(碉堡了的操作,反正我是不知道怎么实现的)

#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)

这个宏定义就是获得对于TYPE类型的变量(应该是个结构体,不然->干嘛),其成member的偏移量

&取地址的操作结果是个右值,就是说&的结果仅仅能放在 赋值符号= 右边,不能放在左边,就这样。

offsetof返回的是当前结构体TYPE成员MEMBER的地址偏移量

const typeof(((type *)0)->member)*__mptr = (ptr);

把__mptr强制类型转换成指向member所在结构体的指针

并把ptr的值赋值给这个指针

(type *)((char *)__mptr - offsetof(type, member));

得到当前member在结构体中,在内存中的地址

<span style="font-size:14px;">/************************************************************************
code writer : EOF
code date : 2014.04.18
e-mail : jasonleaster@gmail.com
code purpose :
This isa simple demo for what container_of is usred for.
If there is something wrong with my code, please touche me by e-mail.
It's pleasure to get your feedback. Thank you. *************************************************************************/ #include <stdio.h> #define offsetof(TYPE,MEMBER) ((size_t) &((TYPE*)0)->MEMBER) #define container_of(ptr,type,member) ({ \
const typeof(((type *)0)->member)*__mptr = (ptr); \
(type *)((char*)__mptr- offsetof( type , member)); } ) int main()
{
struct foo
{
int a;
int b;
int c;
int d;
}; structfoo temp; //-----If we don't know this block, we could also find the address of temp------------------------//
struct foo number = {.a = 1,.b = 2,.c = 3,.d = 4}; temp = number;
//-----------------------------------------------------------------------------------------------// /*******************************************************************************************
Just thinkabout that if we don't know what's the adress of the
structure which contain some element we interestingin. But we knew
the defination of the structure and some elements(eg.the temp
structure's element -- b) of this type structure. Wecould make it
by the macro -- container_of. ********************************************************************************************/ structfoo* we_found = container_of(&temp.b,struct foo,b); //----we could get all message of the container whichthe element we knew located in--------// printf("structure we found\n"); printf("foo.a =%d\n",we_found->a);
printf("foo.b =%d\n",we_found->b);
printf("foo.c =%d\n",we_found->c);
printf("foo.d =%d\n",we_found->d); return0;
}</span>

liuzjian@ubuntu:~/Desktop$ ./a.out

structure we found

foo.a  = 1

foo.b  = 2

foo.c  = 3

foo.d  = 4

这个demo帮我解释了contain_of的用处。

如果有一些我们不知道的结构体部分,他的数据我们是不知道的,好比上面的number,详细细节我们不知道,如果我们仅有当中一个元素的地址,以及这个元素在结构体中定义的相应成员。以及我们知道这个结构体的定义,那么我们就能够获得这个结构体的全部数据细节。

事实上最广的应用应该是内核数据结构,特殊的双向链表的使用了,这里使用container_of.

update:2014年7月29日 凌晨一点

一下是LDD上第三章的一段话,解释了container_of的用途

Fortunately, in this case, the kernel hackers have done the tricky stuff for us, in the form of the container_of macro, defined in <linux/kernel.h> :

container_of(pointer, container_type, container_field);

This macro takes apointer to a field of type container_field, within a structure of type container_type, and returns a pointer to the containing structure.

由linux内核某个片段(container_of)引发的对于C语言的深入理解的更多相关文章

  1. 转载:Linux内核参数的优化(1.3.4)《深入理解Nginx》(陶辉)

    原文:https://book.2cto.com/201304/19615.html 由于默认的Linux内核参数考虑的是最通用的场景,这明显不符合用于支持高并发访问的Web服务器的定义,所以需要修改 ...

  2. linux内核第一宏 container_of

    内核第一宏 list_entry()有着内核第一宏的美称,它被设计用来通过结构体成员的指针来返回结构体的指针.现在就让我们通过一步步的分析,来揭开它的神秘面纱,感受内核第一宏设计的精妙之处. 整理分析 ...

  3. linux内核中宏container_of是干什么的?

    Linux Kernel Version 4.14 1. container_of是干什么的? 已知一个结构体中某个成员的首指针,那么就可以通过宏container_of来获得此结构体的首指针 2 先 ...

  4. linux 内核中一个全局变量引发的性能问题

    为了调试一个功能,在一个内核模块中,增加了一个全局变量,用来统计自有skb池的申请情况. 因为是临时增加,所以没有考虑性能,一开始只是一个fail的统计,数量不多,也不太考虑是否有计数丢失的情况,毕竟 ...

  5. 嵌入式C语言自我修养 04:Linux 内核第一宏:container_of

    4.1 typeof 关键字 ANSI C 定义了 sizeof 关键字,用来获取一个变量或数据类型在内存中所占的存储字节数.GNU C 扩展了一个关键字 typeof,用来获取一个变量或表达式的类型 ...

  6. Linux内核期末总结

    20135316王剑桥<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC 1000029000 第一周 http://www.cn ...

  7. Linux内核期中

    Linux内核期中总结 一.计算机是如何工作的 个人理解:计算机就是通过和用户进行交互,执行用户的指令,这些指令存放在内存中,通过寄存器存储,堆栈变化,来一步步顺序执行. 二.存储程序计算机工作模型 ...

  8. Linux内核启动过程概述

    版权声明:本文原创,转载需声明作者ID和原文链接地址. Hi!大家好,我是CrazyCatJack.今天给大家带来的是Linux内核启动过程概述.希望能够帮助大家更好的理解Linux内核的启动,并且创 ...

  9. 对于Linux内核执行过程的理解(基于fork、execve、schedule等函数)

    382 + 原创作品转载请注明出处 + https://github.com/mengning/linuxkernel/ 一.实验环境 win10 -> VMware -> Ubuntu1 ...

随机推荐

  1. loadrunner下检查点乱码情况处理

    对于很多用过LR的人来说,乱码一直是很纠结的事情,尤其是对新手来说.网上给的解决方法是在录制的时候勾选UTF-8选项,但是似乎并没有解决. 对于用户名为中文或者检查点为中文的情况,我们又该如何去处理呢 ...

  2. 前端面试题(JS篇)

    原题地址:http://handyxuefeng.blog.163.com/blog/static/454521722013111714040259/ 好吧,最近打算换工作,所以关注比较多的是面试题, ...

  3. DzzOffice1.0 Beta2 全新安装图文教程及界面简单了解

    本文说明:本文档用于帮助您全新安装完整的 DzzOffice Beta版软件.DzzOffice 是一款开源的云存储与应用管理工具,主要用于企业管理阿里云.亚马逊等云存储等空间,把空间可视化分配给成员 ...

  4. C#获取ftp文件最后修改时间

    public static DateTime GetFileModifyDateTime(string ftpServerIP,string ftpFolder,string ftpUserID,st ...

  5. HDU1243:反恐训练营

    题目链接:反恐训练营 题意:本质上是求最大公共子序列,然后加上一个权值 分析:见代码 //公共子序列问题 //dp[i][j]表示前s1的前i个与s的前j个匹配得到的最大公共子序列 #include& ...

  6. SCAU 10893 Spiral

    10893 Spiral 时间限制:1000MS  内存限制:65535K 题型: 编程题   语言: 无限制 Description Given an odd number n, we can ar ...

  7. 在使用EF开发时候,遇到 using 语句中使用的类型必须可隐式转换为“System.IDisposable“ 这个问题。

    原因就是 这个程序集中没有引用EntityFramework.可以使用Nuget 安装EntityFramewok.

  8. 【转载】Windows/Office“神key的来源”(附win8神key)

        凡是没有经过微软授权的渠道激活Windows/Office的全部是"D版"!但由于密钥激活更方便快捷,因此很受欢迎.从百度博客到现在,很多网友询问:"神key&q ...

  9. POJ1651Multiplication Puzzle(区间DP)

    比较好做的区间DP 状态转移方程:DP[i][j] 表示区间[i,j]最小的乘积和. DP[i][j] = MIN{DP[i][k-1]+DP[k+1][j] + a[k]*a[i-1]*a[j+1] ...

  10. MFC下的各种字符串类型和相互转换

    MFC下的常用字符串数据类型表示的含义: L:Long  长 P:Point  指针 C:Const  常量 W:Wchar_t  宽字符 T:TCHAR  STR:String  字符串 在看看MF ...