1. 前述の通り 数年前、Linux ドライバーのコードを読んでいたときにこのマクロを見ました。長い間 Baidu で検索して使い方は知っていましたが、実装プロセスと原理についてはまだ漠然とした理解しかありませんでした。 container_of マクロは Linux カーネル コードで非常に多く使用されています。Linux プログラミングが好きな学生にとって、その実装方法を理解することは、将来カーネル コードを読んだりカーネル ドライバーを書いたりするときに非常に役立ちます。もちろん、これを理解したら何でもできると言っているわけではありません。カーネルは奥が深く、広範囲です。まずマクロから学び、次にミクロを学ぶ必要があります。一口で太っちょになれるとは思わないでください。この記事では主にこの関数の実装原理を分析しており、皆さんの学習プロセスに役立つことを願っています。 Android7.1/カーネル/ドライバー/入力 カーネル/ドライバー/入力$ grep -rn container_of ./|wc -l 710 android7.1/カーネル/ドライバー/入力$ grep -rn container_of ./|wc -l を使用して、kernel/drivers/input/ ディレクトリに container_of が出現する回数をカウントします。合計で 710 回使用されています。 2. container_ofの役割 container_of の機能は、構造体のメンバ変数のアドレスを介して構造体のアドレスを取得することです。あなたの名前が Li Guangming で、XXX という弟がいるとします。警察の叔父は、あなたの弟 XXX が悪いことをしたと知りましたが、警察の叔父はあなたの弟の名前を知らなかったため、あなたを逮捕して尋問しましたが、あなたは非常に頑固で、彼に話すことを拒否しました。警察の叔父はあなたの名前を入手し、あなたの家族の戸籍簿を調べたところ、あなたの弟が見つかりました。あなたの弟 XXX の名前は Li Xiaoming であることが判明しました。この事件解決方法は手がかりを追う方法と呼ばれます。 カーネル関数の呼び出しでは、構造体のメンバーのアドレスが渡されることが多く、その後関数は構造体内の他のメンバー変数を使用する必要があるため、この問題が発生します。これは、C でオブジェクト指向プログラミングを実装する方法でもあると思います。 例えばこのコード 静的voidセンサーサスペンド(構造体early_suspend *h) { 構造体sensor_private_data *センサー = container_of(h、構造体sensor_private_data、早期サスペンド); if (センサー->オペレーション->サスペンド) センサー->ops->suspend(センサー->クライアント); } early_suspend は、sensor_private_data のメンバーです。sensor_private_data 構造体変数のアドレスは、このメンバーのアドレスを通じて取得され、それによって内部のメンバー変数 client が呼び出されます。この方法は非常にエレガントです。ここでは、よりエレガントな言葉「エレガント」を使いました。 ここで簡単に説明すると、渡された h はどこか別の場所で定義されていて、オペレーティング システムがそれにメモリ領域を割り当てている必要があります。h に領域を割り当てるということは、その親にもメモリがあることを意味します。そうでなければ、手がかりをたどって NULL を見つけるのは愚かなことです。 3. container_ofの使い方 Container_of は 3 つのパラメータを渡す必要があります。最初のパラメータはポインタ、2 番目のパラメータは構造体の型、3 番目のパラメータは 2 番目のパラメータに対応する構造体のメンバーです。
4. コンテナで使用される知識ポイントの分析 4.1. ({}) の役割 ({}) まず、この式について説明します。多くの人が理解し、さまざまな場所で見たことがあるかもしれませんが、注目していないかもしれません。この式は、最後の式の値を返します。たとえば、x=({a;b;c;d;}) の場合、x の最終値は d になります。 コード例: #include <stdio.h> void main(void) { 整数a = ({1;2;4;}) + 10; printf("%d\n",a);//a=14 } 4.2. typeofは変数の型を取得します これはめったに見られません。このキーワードはC言語のキーワードの拡張であり、変数の型を返します。詳細については、GCCの紹介を参照してください。 ++式の型を参照する別の方法はtypeofを使用することです。このキーワードを使用する構文はsizeofに似ていますが、構文はtypedefで定義された型名のように意味的に動作します。++ コード例: void main(void) { 整数a = 6; typeof(a) b =9; printf("%d %d\n",a,b); } 4.3. (struct st*)0の役割 誰もが定規を使ったことがあると思います。例えば、定規を使って本の長さを測りたい場合、まず定規の0目盛りの位置を見つけ、この0目盛りの位置を使って本の端を合わせ、揃える必要があります。本の反対側にある定規の目盛りを確認すると、本の長さがわかります。 次に、構造物の長さを測定する必要があります。定規を使用して測定することもできます。0 目盛りの位置を見つけるだけです。同様に、0 スケールの位置がわからなくても、最初のスケールと最後のスケールを減算することで構造の長さを計算できます。 しかし、C の定規とは何でしょうか? sizeof を思い浮かべるかもしれませんが、残念ながらこれは私たちのニーズを満たしていないので、定規として本当に完璧な (struct st *) があります。 構造体st{ 整数a; 整数 b; }*p_st、n_st; void main(void) { printf("%p\n",&((struct st*)0)->b); } 上記のコード (構造体st*)0 これは、この構造を 0 スケールに置いて測定を開始することを意味します。では、どこまで測定するのでしょうか? &((構造体st*)0)->b) これは、位置 b まで測定するときに反映されます。したがって、上記の出力は 4 になります。 上記の説明を読んだ後、次の 2 つのコードは同じ機能を持っていることがわかります。 typeof ((struct st*)0)->b) c; // bの型を取ってcを宣言する 整数c; 実際、これは 0 だけではなく、他の数値にも当てはまります。たとえば、次のコードでは、コンパイラは数値ではなく型を重視します。 printf("%p\n",&((struct st*)4)->b -4); この記事は数日前に書いたものですが、この核心を証明する特に良い方法が見つからなかったため、直接投稿したくありませんでした。上記を読んだ後、この種の測定についてある程度理解できたはずです。配列の最初のアドレスを 0 に設定する必要がある場合、どうすればよいでしょうか。 数分の遅延があると仮定して、少し考えてみましょう。 コードは次のとおりです。 構造体A{ 短い配列[100]; }; int main(int argc, char *argv[]) { 整数 i = 10; A*a = (A*)0; printf("%p %d %d\n",a,sizeof(short), &a->array[20]); getchar(); 1 を返します。 } // 出力 00000000 2 40 ==struct A *== を使用せずに、配列のアドレスを位置 0 に直接配置する方法はありますか?まだこれより良い解決策は見つかっていません。何か良い提案があれば、メッセージを残してください。 4.4、オフセット(タイプ、メンバー) #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE*)0)->MEMBER) size_t が分からない場合は、Baidu で検索してみてください。符号なし整数です。32 ビットと 64 ビットでは長さが異なるため、構造体のオフセット長を取得するには offsetof を使用します。 4.5. const int* p の関数 上記のマクロ定義には小さな知識ポイントもあります const typeof( ((type *)0)->member ) *__mptr 上記のコードは次のように短縮できます。 定数 int * __mptr これは何を示しているのでしょうか?これは、__mptr が指す整数データが const (定数) であることを示します。 これにはさらに2つの知識が関係する int * const __mptr; // は __mptr の値が変更できないことを意味します // また const int * const __mptr; // は __mptr が変更できず、それが指すコンテンツも変更できないことを意味します 5. container_ofの分析 上記の知識ポイントを読むと、container_of マクロが非常に明確になります。解析部分を以下のコードコメントに記します。 #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE*)0)->MEMBER) #define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (const typeof( ((type *)0)->member ) *)(ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) //----- 区切り線 struct st{ 整数a; 整数 b; }*pt; //例としてこれを使用します container_of(&pt->a,struct st,a) const typeof( ((struct st *)0)->a ) *__mptr = (const typeof( ((struct st *)0)->a ) *)(&pt->a); const int *__mptr = (int *)(&pt->a);//最初の文を解析した後、実際に a のアドレスを取得します。 (type *)( (char *)__mptr - offsetof(type,member) ); //これは (struct st *)( (char *)__mptr - ((unsigned int) &((struct st*)0)->a)); になります。 //この文は、a のアドレスから構造体への a のオフセット アドレス長を引いたものが構造体のアドレス位置であることを意味します。 6. サンプルコード 上記の説明を読んだ後、少なくともこのマクロの感覚はつかめるはずです。コードを書いてテストし、コードに自分を統合してください。この方法でのみ、人間とコードの一体化の状態を実現できます。 コードは次のとおりです。 #include <stdio.h> #include<stddef.h> #include<stdlib.h> #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE*)0)->MEMBER) /*ptr メンバ ポインタ* 型構造体 (struct Stu など) * メンバー メンバー変数、ポインタに対応* */ #define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (const typeof( ((type *)0)->member ) *)(ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) typedef 構造体 Stu{ 年齢; 文字名[10]; 整数ID; 符号なしロング電話番号; }*p_stu、str_stu; void print_all(void *p_str) { p_stu m1p_stu = NULL; m1p_stu = container_of(p_str、構造体Stu、年齢); printf("年齢:%d\n",m1p_stu->年齢); printf("名前:%s\n",m1p_stu->名前); printf("id:%d\n",m1p_stu->id); printf("phone_num:%d\n",m1p_stu->phone_num); } void main(void) { p_stu m_stu = (p_stu)malloc(sizeof(str_stu)); m_stu->年齢 = 25; m_stu->id = 1; m_stu->name[0] = 'w'; m_stu->名前[1]='e'; m_stu->名前[2]='i'; m_stu->名前[3]='q'; m_stu->名前[4]='i'; m_stu->名前[5]='f'; m_stu->名前[6]='a'; m_stu->name[7]='\0'; m_stu->電話番号=13267; /* 構造体メンバーのポインタを渡す */ print_all(&m_stu->年齢); printf("メイン終了\n"); if(m_stu!=NULL) m_stuを解放します。 } 7. プログラム出力
要約する 以上がこの記事の全内容です。この記事の内容が皆様の勉強や仕事に何らかの参考学習価値をもたらすことを願います。123WORDPRESS.COM をご愛顧いただき、誠にありがとうございます。これについてもっと知りたい場合は、次のリンクをご覧ください。 以下もご興味があるかもしれません:
|
>>: Windows 10 での mysql5.5 データベース コマンドラインの中国語文字化け問題を解決する
目次初期化初期化状態()初期化プロパティ()初期化データ()観察する()オブザーバーリアクティブを定...
watch : データの変更を監視する(特定の値の変更イベント) vue2.x データ(){ 戻る ...
これにより、png ファイルのアップロードも不可能になりました (後で情報を調べたところ、レジストリ...
目次1. Linuxシステムの操作レベルの概要2. 実行レベルを確認する3. 現在のシステムの動作レ...
MySQL テーブルのテーブル構造をすばやく変更する - 「MySQL 管理」から抜粋 ALTER ...
プロジェクトはサーバーと対話し、post を通じてサーバー側の jsp にアクセスし、jsp はサー...
メタタグ機能METAタグは、HTMLタグのHEAD領域にある重要なタグです。文書の文字セット、使用言...
初心者は、いくつかの HTML タグを理解することで HTML を学習できます。この入門書は、初心者...
この記事では、参考までにMySQLのインストールと設定のチュートリアルを紹介します。具体的な内容は次...
アバターをアップロードするにはVue-Cropperコンポーネントを使用します。参考までに具体的な内...
<br />原文: http://andymao.com/andy/post/102.h...
Prometheus (プロメテウスとも呼ばれる) 公式サイト: https://prometheu...
目次古典的なアプローチ質問その他の質問注意が必要な問題古典的なアプローチご存知のとおり、アカウントの...
1. my.iniファイルを手動で作成して追加する # クライアントセクション # --------...
この記事では、シンプルなカルーセル効果を実現するためのjsの具体的なコードを参考までに紹介します。具...