C言語の共用体unionの使い方と実例を教えて!
こういった悩みにお答えします.
本記事の信頼性
- リアルタイムシステムの研究歴12年.
- 東大教員の時に,英語でOS(Linuxカーネル)の授業.
- 2012年9月~2013年8月にアメリカのノースカロライナ大学チャペルヒル校(UNC)コンピュータサイエンス学部で客員研究員として勤務.C言語でリアルタイムLinuxの研究開発.
- プログラミング歴15年以上,習得している言語: C/C++,Python,Solidity/Vyper,Java,Ruby,Go,Rust,D,HTML/CSS/JS/PHP,MATLAB,Verse(UEFN), Assembler (x64,ARM).
- 東大教員の時に,C++言語で開発した「LLVMコンパイラの拡張」,C言語で開発した独自のリアルタイムOS「Mcube Kernel」をGitHubにオープンソースとして公開.
- 2020年1月~現在はアメリカのノースカロライナ州チャペルヒルにあるGuarantee Happiness LLCのCTOとしてECサイト開発やWeb/SNSマーケティングの業務.2022年6月~現在はアメリカのノースカロライナ州チャペルヒルにあるJapanese Tar Heel, Inc.のCEO兼CTO.
- 最近は自然言語処理AIとイーサリアムに関する有益な情報発信に従事.
- (AI全般を含む)自然言語処理AIの論文の日本語訳や,AIチャットボット(ChatGPT,Auto-GPT,Gemini(旧Bard)など)の記事を50本以上執筆.アメリカのサンフランシスコ(広義のシリコンバレー)の会社でプロンプトエンジニア・マネージャー・Quality Assurance(QA)の業務委託の経験あり.
- (スマートコントラクトのプログラミングを含む)イーサリアムや仮想通貨全般の記事を200本以上執筆.イギリスのロンドンの会社で仮想通貨の英語の記事を日本語に翻訳する業務委託の経験あり.
こういった私から学べます.
C言語を独学で習得することは難しいです.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!
目次
共用体union
共用体unionは,複数の異なる型とサイズの変数を同じアドレスを共有するように保持します.
つまり,union型は異なる型とサイズのデータを(ある瞬間には1つだけ)保持する変数です.
共用体の扱いは,文法的には構造体とほぼ同じです.
共用体unionの使い方
共用体unionの使い方を紹介します.
共用体unionの利用したコード例は以下になります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #include <stdint.h> union udouble { double d; long l; uint8_t s[sizeof(double)]; }; int main(void) { union udouble ud = {1.0}; int i; printf("ud.d = %lf\n", ud.d); printf("ud.l = %ld (0x%lx)\n", ud.l, ud.l); for (i = 0; i < sizeof(double); i++) { printf("%02x ", ud.s[i]); } printf("\n"); return 0; } |
実行結果は以下になります.
1 2 3 4 5 |
$ gcc union.c $ a.out ud.d = 1.000000 ud.l = 4607182418800017408 (0x3ff0000000000000) 00 00 00 00 00 00 f0 3f |
3行目でdouble型の1.0を出力,4行目でdouble型の1.0をlong型で出力したところ,「4607182418800017408 (0x3ff0000000000000)」になりました.
これは,浮動小数点数を表現する仕様のIEEE 754で1.0という意味です.
浮動小数点数内部表現シミュレーターで10進数の1.0を入力すると同じ結果になることが確認できます.
また,5行目で各々のバイトをuint8_t型で表示したところ,「00 00 00 00 00 00 f0 3f」とバイトオーダーが逆になっていることがわかります.
この理由は,私の実行環境のIntel CPUでは,リトルエンディアンでバイトデータを格納するからです.
共用体unionのLinuxカーネルにおける実例
共用体unionは,どのような場合に使われるのか知りたくありませんか?
そんなあなたにLinuxカーネルにおける共用体unionの実例を紹介していきます.
include/linux/sched.hで共用体union rcu_specialが定義されています.
1 2 3 4 5 6 7 8 9 |
union rcu_special { struct { u8 blocked; u8 need_qs; u8 exp_hint; /* Hint for performance. */ u8 need_mb; /* Readers need smp_mb(). */ } b; /* Bits. */ u32 s; /* Set of bits. */ }; |
union rcu_specialの中身は以下になります.
- b:無名構造体structの変数(サイズは32ビット)
- blocked,need_qs,exp_hint,need_mb:変数bが持つ4つの符号なし8ビット整数型(u8型)のメンバ変数
- s:符号なし32ビット型整数型(u32型)のメンバ変数
つまり,union rcu_specialはサイズが32ビットの無名構造体bと符号なし整数として操作できます.
また,無名構造体は4つの符号なし整数型のメンバ変数を持つので,それぞれのメンバ変数を8ビット(1バイト)単位で操作できます.
実際にunion rcu_specialを操作しているrcu_preempt_deferred_qs_irqstore関数(kernel/rcu/tree_plugin.h)のコードは以下になります.
18行目でunion rcu_special型の変数specialにt->rcu_read_unlock_specialの値を格納しています.
また,specialは20行目と44行目,t->rcu_read_unlock_specialは20行目と24行目で,それぞれ読み書きしていることがわかります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
static void rcu_preempt_deferred_qs_irqrestore(struct task_struct *t, unsigned long flags) { bool empty_exp; bool empty_norm; bool empty_exp_now; struct list_head *np; bool drop_boost_mutex = false; struct rcu_data *rdp; struct rcu_node *rnp; union rcu_special special; /* * If RCU core is waiting for this CPU to exit its critical section, * report the fact that it has exited. Because irqs are disabled, * t->rcu_read_unlock_special cannot change. */ special = t->rcu_read_unlock_special; rdp = this_cpu_ptr(&rcu_data); if (!special.s && !rdp->exp_deferred_qs) { local_irq_restore(flags); return; } t->rcu_read_unlock_special.s = 0; if (special.b.need_qs) { if (IS_ENABLED(CONFIG_RCU_STRICT_GRACE_PERIOD)) { rcu_report_qs_rdp(rdp); udelay(rcu_unlock_delay); } else { rcu_qs(); } } /* * Respond to a request by an expedited grace period for a * quiescent state from this CPU. Note that requests from * tasks are handled when removing the task from the * blocked-tasks list below. */ if (rdp->exp_deferred_qs) rcu_report_exp_rdp(rdp); /* Clean up if blocked during RCU read-side critical section. */ if (special.b.blocked) { /* * Remove this task from the list it blocked on. The task * now remains queued on the rcu_node corresponding to the * CPU it first blocked on, so there is no longer any need * to loop. Retain a WARN_ON_ONCE() out of sheer paranoia. */ rnp = t->rcu_blocked_node; raw_spin_lock_rcu_node(rnp); /* irqs already disabled. */ WARN_ON_ONCE(rnp != t->rcu_blocked_node); WARN_ON_ONCE(!rcu_is_leaf_node(rnp)); empty_norm = !rcu_preempt_blocked_readers_cgp(rnp); WARN_ON_ONCE(rnp->completedqs == rnp->gp_seq && (!empty_norm || rnp->qsmask)); empty_exp = sync_rcu_exp_done(rnp); smp_mb(); /* ensure expedited fastpath sees end of RCU c-s. */ np = rcu_next_node_entry(t, rnp); list_del_init(&t->rcu_node_entry); t->rcu_blocked_node = NULL; trace_rcu_unlock_preempted_task(TPS("rcu_preempt"), rnp->gp_seq, t->pid); if (&t->rcu_node_entry == rnp->gp_tasks) WRITE_ONCE(rnp->gp_tasks, np); if (&t->rcu_node_entry == rnp->exp_tasks) WRITE_ONCE(rnp->exp_tasks, np); if (IS_ENABLED(CONFIG_RCU_BOOST)) { /* Snapshot ->boost_mtx ownership w/rnp->lock held. */ drop_boost_mutex = rt_mutex_owner(&rnp->boost_mtx) == t; if (&t->rcu_node_entry == rnp->boost_tasks) WRITE_ONCE(rnp->boost_tasks, np); } /* * If this was the last task on the current list, and if * we aren't waiting on any CPUs, report the quiescent state. * Note that rcu_report_unblock_qs_rnp() releases rnp->lock, * so we must take a snapshot of the expedited state. */ empty_exp_now = sync_rcu_exp_done(rnp); if (!empty_norm && !rcu_preempt_blocked_readers_cgp(rnp)) { trace_rcu_quiescent_state_report(TPS("preempt_rcu"), rnp->gp_seq, 0, rnp->qsmask, rnp->level, rnp->grplo, rnp->grphi, !!rnp->gp_tasks); rcu_report_unblock_qs_rnp(rnp, flags); } else { raw_spin_unlock_irqrestore_rcu_node(rnp, flags); } /* Unboost if we were boosted. */ if (IS_ENABLED(CONFIG_RCU_BOOST) && drop_boost_mutex) rt_mutex_futex_unlock(&rnp->boost_mtx); /* * If this was the last task on the expedited lists, * then we need to report up the rcu_node hierarchy. */ if (!empty_exp && empty_exp_now) rcu_report_exp_rnp(rnp, true); } else { local_irq_restore(flags); } } |
Linuxカーネルを学びたいあなたは,こちらからどうぞ.
まとめ
C言語で共用体unionの使い方とLinuxカーネルにおける実例を紹介しました.
共用体unionのdouble型で格納した値をlong型で出力することで,IEEE 754に関する理解を深めました.
また,Linuxカーネルにおける実例を読むことで,共用体unionが有用であることがわかりました.
C言語を独学で習得することは難しいです.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!