The Magical container_of() Macro and typeof()

When you begin with the kernel, and you start to look around and read thecode, you will eventually come across this magical preprocessor construct.

What does it do? Well, precisely what its name indicates. It takes threearguments – a pointer, type of the container, and the name of the memberthe pointer refers to. The macro will then expand to a new address pointing tothe container which accommodates the respective member. It is indeed aparticularly clever macro, but how the hell can this possibly work? Let meillustrate…

The first diagram illustrates the principle of the container_of(ptr, type,member) macro for who might find the above description too clumsy.

The container_of macro Illustration of how the containter_of macro works.

Bellow is the actual implementation of the macro from Linux Kernel:

#define container_of(ptr, type, member) ({                      \
        const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
        (type *)( (char *)__mptr - offsetof(type,member) );})

At first glance, this might look like a whole lot of magic, but it isn’t quiteso. Let’s take it step by step.

Statements in Expressions

The first thing to gain your attention might be the structure of the wholeexpression. The statement should return a pointer, right? But there is justsome kind of weird ({}) block with two statements in it. This in fact is aGNU extension to C language called braced-group within expression. Thecompiler will evaluate the whole block and use the value of the last statementcontained in the block. Take for instance the following code. It will print 5.

int x = ({1; 2;}) + 3;
printf("%d\n", x);

typeof()

This is a non-standard GNU C extension. It takes one argument and returns itstype. Its exact semantics is throughly described in gccdocumentation.

int x = 5;
typeof(x) y = 6;
printf("%d %d\n", x, y);

Zero Pointer Dereference

But what about the zero pointer dereference? Well, it’s a little pointer magicto get the type of the member. It won’t crash, because the expression itselfwill never be evaluated. All the compiler cares for is its type. The samesituation occurs in case we ask back for the address. The compiler againdoesn’t care for the value, it will simply add the offset of the member to theaddress of the structure, in this particular case 0, and return the newaddress.

struct s {
        char m1;
        char m2;
};

/* This will print 1 */
printf("%d\n", &((struct s*)0)->m2);

Also note that the following two definitions are equivalent:

typeof(((struct s *)0)->m2) c;
char c;

offsetof(st, m)

This macro will return a byte offset of a member to the beginning of thestructure. It is even part of the standard library (available in stddef.h ).Not in the kernel space though, as the standard C library is not present there.It is a little bit of the same 0 pointer dereference magic as we saw earlierand to avoid that modern compilers usually offer a built-in function, thatimplements that. Here is the messy version (from the kernel):

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

It returns an address of a member called MEMBER of a structure of type TYPEthat is stored in memory from address 0 (which happens to be the offset we’relooking for).

Putting It All Together

#define container_of(ptr, type, member) ({                      \
        const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
        (type *)( (char *)__mptr - offsetof(type,member) );})

When you look more closely at the original definition from the beginning ofthis post, you will start wondering if the first line is really good foranything. You will be right. The first line is not intrinsically important forthe result of the macro, but it is there for type checking purposes. And whatthe second line really does? It subtracts the offset of the structure’smember from its address yielding the address of the container structure.That’s it!

After you strip all the magical operators, constructs and tricks, it is thatsimple :-).

References


转自:http://broken.build/2012/11/10/magical-container_of-macro/
为了解决这个问题,我们可以先对两个数组进行排序,然后用双指针法来找到所有相似的子数组。具体实现如下: ```c #include <stdio.h> #include <stdlib.h> #define MAX_N 1000000 int M[MAX_N], N[MAX_N]; int m, n; int compare(const void *a, const void *b) { return *(int*)a - *(int*)b; } int count_similar() { int i = 0, j = 0; int count = 0; while (i < m && j < n) { if (M[i] == N[j]) { // 找到相同的元素 int k = j; while (k < n && N[k] == N[j]) { k++; } count += k - j; // 加上与N[j]相同的元素的个数 i++; j = k; } else if (M[i] < N[j]) { // M[i]较小,需要增加N中的元素 i++; } else { // M[i]较大,需要减少N中的元素 j++; } } return count; } int main() { scanf("%d%d", &m, &n); for (int i = 0; i < m; i++) { scanf("%d", &M[i]); } for (int i = 0; i < n; i++) { scanf("%d", &N[i]); } qsort(M, m, sizeof(int), compare); qsort(N, n, sizeof(int), compare); printf("%d\n", count_similar()); return 0; } ``` 首先读入两个数组的长度和元素,然后对它们进行排序。然后用双指针法找到所有相似的子数组,具体方法如下: 1. 初始化两个指针i和j,分别指向M和N的开头; 2. 如果M[i]等于N[j],说明找到了一个相似的子数组,接着向右移动j,直到N[j]不等于N[j+1]; 3. 如果M[i]小于N[j],说明N[j]需要增加,所以向右移动i; 4. 如果M[i]大于N[j],说明N[j]需要减少,所以向右移动j; 5. 重复2~4步,直到i到达M的末尾或j到达N的末尾。 遍历过程中,我们用一个计数器count来累计相似的子数组的个数,每当找到一个相似的子数组时,我们就把与N[j]相同的元素的个数加到count中。最后返回count即可。 时间复杂度为O(MlogM + NlogN),空间复杂度为O(M + N)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值