1 概要
通常使用しているOS Mintで Cのプログラムを動作確認できる簡単な方法を記載する。
2 Visual Studio Codeでプログラム
Visual Studio Code(以下 VSC と言う)で開発しようと思い拡張機能を確認すると
c/c++をインストールしようと拡張機能を確認するとすでにインストールされていた。

私は以前から使用しており、何時の間にかもう環境が整っているようなので、このC/C++の使い方を確認してみる。
・ユーザ名のところにディレクトリーを作成 名前は c_program
・ファイルを作成 名前は test.c
・テストコードを書く
・VCSの左側に有る ▷ 印をクリック
・実行のエリアが表示され、
そこにある『実行とデバッグ』をクリックする。
実行ボタンを押すとgccの選択が表示された。
gcc と gcc-12 どれを選択するか?
私は特別な理由がなければ「gcc」を選択する。
実行とデバッグ、gccの選択画面

実行すると下記図にコメントが表示される。

c/c++をインストールしようと拡張機能を確認するとすでにインストールされていた。

私は以前から使用しており、何時の間にかもう環境が整っているようなので、このC/C++の使い方を確認してみる。
・ユーザ名のところにディレクトリーを作成 名前は c_program
・ファイルを作成 名前は test.c
・テストコードを書く
・VCSの左側に有る ▷ 印をクリック
・実行のエリアが表示され、
そこにある『実行とデバッグ』をクリックする。
実行ボタンを押すとgccの選択が表示された。
gcc と gcc-12 どれを選択するか?
私は特別な理由がなければ「gcc」を選択する。
gcc(デフォルトのバージョン)は、お使いのLinux Mintシステムで最も安定して動作するように設計・テストされているバージョンだ。
通常、システムのパッケージやライブラリはデフォルトのGCCバージョンでコンパイルされており、互換性の問題が発生しにくい。
通常、システムのパッケージやライブラリはデフォルトのGCCバージョンでコンパイルされており、互換性の問題が発生しにくい。
実行とデバッグ、gccの選択画面

実行すると下記図にコメントが表示される。

わたしは実行ファイルまで作成する必要が無いので、今の所はここで終了する。
2.1 gcc
ステムにインストールされているデフォルトまたは推奨のGCCバージョンへのシンボリックリンク(エイリアス)です。
Linux Mintのような長期サポート(LTS)を重視するディストリビューションでは、安定性が確認された特定のバージョン (例えば、Linux Mint 21.xではGCC 11、Linux Mint 22ではGCC 13など)がデフォルトとして設定される。
Linux Mintのような長期サポート(LTS)を重視するディストリビューションでは、安定性が確認された特定のバージョン (例えば、Linux Mint 21.xではGCC 11、Linux Mint 22ではGCC 13など)がデフォルトとして設定される。
2.2 gcc-12
GCCコンパイラの特定のバージョン12を指します。
バージョン12は、以前のバージョンと比較して、新しい言語機能のサポート、バグ修正、そして潜在的な最適化の改善が含まれている。
システムによっては、デフォルト以外の新しい(または古い)バージョンをインストールして切り替えられるように設定されている場合がある。
バージョン12を選択する場合
特定のソフトウェア開発者で、GCC 12の新しい機能や最適化を意図的に利用したい場合に限られる。
メディア再生ソフトウェアの実行時にこの選択肢が出た場合、そのソフトウェア(またはその依存関係)が新しいコンパイラ機能を必要としている可能性もわずかにありますが、デフォルトで問題ないことがほとんどです。
バージョン12は、以前のバージョンと比較して、新しい言語機能のサポート、バグ修正、そして潜在的な最適化の改善が含まれている。
システムによっては、デフォルト以外の新しい(または古い)バージョンをインストールして切り替えられるように設定されている場合がある。
バージョン12を選択する場合
特定のソフトウェア開発者で、GCC 12の新しい機能や最適化を意図的に利用したい場合に限られる。
メディア再生ソフトウェアの実行時にこの選択肢が出た場合、そのソフトウェア(またはその依存関係)が新しいコンパイラ機能を必要としている可能性もわずかにありますが、デフォルトで問題ないことがほとんどです。
3 プログラミングでの疑問点
3.1 ディレクティブ
ディレクティブ(Directive)とは、
プログラミングにおいてコンパイラや言語処理系(プリプロセッサ、JSPコンテナ、フレームワークなど)に対して、 ソースコードの処理方法や動作を指示・設定するための特別な命令文です。
ソースコード内に記述されますが、実行可能なプログラムには直接反映されず、 処理の前にファイルのマージ(#include)や定数の定義(#define)、コンパイルや変換の動作を制御します。
MCUなどメモリ容量が極めて少ないマイコンでは、不要なコードをコンパイル対象から外して容量を節約するために多用されます。
C言語の#includeや 先頭に # がついたものが該当する。
プログラミングにおいてコンパイラや言語処理系(プリプロセッサ、JSPコンテナ、フレームワークなど)に対して、 ソースコードの処理方法や動作を指示・設定するための特別な命令文です。
ソースコード内に記述されますが、実行可能なプログラムには直接反映されず、 処理の前にファイルのマージ(#include)や定数の定義(#define)、コンパイルや変換の動作を制御します。
MCUなどメモリ容量が極めて少ないマイコンでは、不要なコードをコンパイル対象から外して容量を節約するために多用されます。
C言語の#includeや 先頭に # がついたものが該当する。
3.1.1 #include
ヘッダーファイルにより関数をソースコードファイルに組み込むための命令です。
例えば
uint16_t や uint8_t のような固定幅整数型が「定義されていない」と表示される場合、必要なヘッダーファイルがインクルードされていないのが原因。
これらの型を使用するには、コードの先頭で標準ヘッダーである < stdint.h > をインクルードする必要がある。
#include < stdint.h >
を最初の行に追加して、ファイルを書き込みするとエラーは消える。
例えば
uint16_t や uint8_t のような固定幅整数型が「定義されていない」と表示される場合、必要なヘッダーファイルがインクルードされていないのが原因。
これらの型を使用するには、コードの先頭で標準ヘッダーである < stdint.h > をインクルードする必要がある。
#include < stdint.h >
を最初の行に追加して、ファイルを書き込みするとエラーは消える。
3.1.2 #define
#define ******
識別子を定義します。値を持たせることも、定義の存在自体を示すことも可能です
#define USE_UART 1 // 値を1として定義
#define DEBUG_MODE // 存在のみ定義(フラグとして使用)
識別子を定義します。値を持たせることも、定義の存在自体を示すことも可能です
#define USE_UART 1 // 値を1として定義
#define DEBUG_MODE // 存在のみ定義(フラグとして使用)
3.1.3 #ifdef
プリプロセッサ指令(#ifdef, #elif, #endif)は、特定の条件が満たされた場合のみコードをコンパイル対象に含めるために使用します。
#ifdef: 指定した名前が #define で定義されていれば、その後のコードを有効にします。
#elif: 「else if」の略です。直前の条件が偽で、かつ新たな条件が真の場合に実行します。
#endif: 条件ブロックの終わりを示します。
コード例
デバッグ時のみ特定の処理を有効にしたり、ハードウェアのピン構成を切り替えたりする場合によく使われます。
#define DEBUG_MODE // この行をコメントアウトするとDEBUG部分が無効になる
void setup() {
#ifdef DEBUG_MODE
printf("Debug Start\n"); // DEBUG_MODEが定義されている時だけコンパイルされる
#elif defined(RELEASE_MODE)
// RELEASE_MODEが定義されている時の処理
#else
// どちらも定義されていない時の処理
#endif
}
注意点:
#ifdef には直接条件式(== など)は書けません。
数値を比較したい場合は #if (VALUE == 1) を使い、#elif と組み合わせて記述します。
#ifdef: 指定した名前が #define で定義されていれば、その後のコードを有効にします。
#elif: 「else if」の略です。直前の条件が偽で、かつ新たな条件が真の場合に実行します。
#endif: 条件ブロックの終わりを示します。
コード例
デバッグ時のみ特定の処理を有効にしたり、ハードウェアのピン構成を切り替えたりする場合によく使われます。
#define DEBUG_MODE // この行をコメントアウトするとDEBUG部分が無効になる
void setup() {
#ifdef DEBUG_MODE
printf("Debug Start\n"); // DEBUG_MODEが定義されている時だけコンパイルされる
#elif defined(RELEASE_MODE)
// RELEASE_MODEが定義されている時の処理
#else
// どちらも定義されていない時の処理
#endif
}
注意点:
#ifdef には直接条件式(== など)は書けません。
数値を比較したい場合は #if (VALUE == 1) を使い、#elif と組み合わせて記述します。
3.1.4 #ifndef
プリプロセッサ指令(#ifdef, #elif, #endif)は、特定の条件が満たされていない場合のみコードをコンパイル対象に含めるために使用します。
#ifndef *** #endif
(もし定義されていなければ)
「if not defined」の略です。多重インクルード防止(ヘッダーガード)に最も使われます。
#ifndef MY_HEADER_H // もし MY_HEADER_H が定義されていなければ
#define MY_HEADER_H // MY_HEADER_H を定義する
// ここにヘッダーの内容を書く
#endif
#ifndef *** #elif defined *** #else *** #endif
(複数条件の分岐)
複雑な条件分岐に使用します。
#if defined(CH32V003)
// CH32V003用の処理
#elif defined(CH32V203)
// CH32V203用の処理
#else
// それ以外の処理
#endif
#ifndef *** #endif
(もし定義されていなければ)
「if not defined」の略です。多重インクルード防止(ヘッダーガード)に最も使われます。
#ifndef MY_HEADER_H // もし MY_HEADER_H が定義されていなければ
#define MY_HEADER_H // MY_HEADER_H を定義する
// ここにヘッダーの内容を書く
#endif
#ifndef *** #elif defined *** #else *** #endif
(複数条件の分岐)
複雑な条件分岐に使用します。
#if defined(CH32V003)
// CH32V003用の処理
#elif defined(CH32V203)
// CH32V203用の処理
#else
// それ以外の処理
#endif
3.1.5 #if
プリプロセッサディレクティブと呼ばれ、コンパイル時に実行される「条件付きコンパイル」を実現するために使用されます。
これにより、特定の条件に基づいてソースコードの一部をコンパイルに含めたり除外したりできます。
例えば
1.デバッグコードの有効・無効切り替え
開発中はデバッグ情報を出力し、リリース時には出力しないようにする場合に使います。
#include "debug.h" // UART出力などのデバッグ機能を提供するヘッダー
// debug.h またはプロジェクト設定で定義
// #define DEBUG_MODE 1
void some_function(void) {
// ... 処理 ...
#if DEBUG_MODE // DEBUG_MODEが1の場合のみコンパイル
printf("DEBUG: some_functionが呼び出されました。\n");
#endif
// ... 処理 ...
}
2. ハードウェアバージョンの違いによるコード切り替え
複数の異なるハードウェアリビジョンやボードタイプに対応する場合、ピン配置などの違いを吸収できます。
// プロジェクト設定、または個別のヘッダーファイルで定義
// #define BOARD_V1
#ifdef BOARD_V1 // BOARD_V1が定義されているかをチェック
#define LED_PIN GPIO_Pin_1
#define UART_BAUDRATE 115200
#elif defined(BOARD_V2) // BOARD_V2が定義されているかをチェック
#define LED_PIN GPIO_Pin_2
#define UART_BAUDRATE 9600
#else // どちらも定義されていない場合
#error "Board version not defined!" // コンパイルエラーを発生させる
#endif
void setup(void) {
// 定義されたマクロを使用してピンを設定
GPIO_Init(GPIOD, LED_PIN, GPIO_Mode_Out_PP);
// ...
}
#ifdefは#if defined(...)の短縮形です。
#errorは、予期しないコンパイル条件の際にエラーメッセージを出力し、コンパイルを停止させるために使用できます
3. コードブロックの一時的なコメントアウト
特定のコードブロックを一時的にコンパイル対象から外したいが、削除はしたくない場合、#if 0と#endifで囲む手法がよく使われます。
void complex_feature(void) {
// この機能を一時的に無効にする
#if 0
// ここから #endif までのコードはコンパイルされません
// 大量のコードや複数行のコメントアウトに便利です
setup_peripheral_A();
while(1) {
process_data_A();
}
#endif
// 代替処理など
setup_peripheral_B();
}
これにより、特定の条件に基づいてソースコードの一部をコンパイルに含めたり除外したりできます。
例えば
1.デバッグコードの有効・無効切り替え
開発中はデバッグ情報を出力し、リリース時には出力しないようにする場合に使います。
#include "debug.h" // UART出力などのデバッグ機能を提供するヘッダー
// debug.h またはプロジェクト設定で定義
// #define DEBUG_MODE 1
void some_function(void) {
// ... 処理 ...
#if DEBUG_MODE // DEBUG_MODEが1の場合のみコンパイル
printf("DEBUG: some_functionが呼び出されました。\n");
#endif
// ... 処理 ...
}
2. ハードウェアバージョンの違いによるコード切り替え
複数の異なるハードウェアリビジョンやボードタイプに対応する場合、ピン配置などの違いを吸収できます。
// プロジェクト設定、または個別のヘッダーファイルで定義
// #define BOARD_V1
#ifdef BOARD_V1 // BOARD_V1が定義されているかをチェック
#define LED_PIN GPIO_Pin_1
#define UART_BAUDRATE 115200
#elif defined(BOARD_V2) // BOARD_V2が定義されているかをチェック
#define LED_PIN GPIO_Pin_2
#define UART_BAUDRATE 9600
#else // どちらも定義されていない場合
#error "Board version not defined!" // コンパイルエラーを発生させる
#endif
void setup(void) {
// 定義されたマクロを使用してピンを設定
GPIO_Init(GPIOD, LED_PIN, GPIO_Mode_Out_PP);
// ...
}
#ifdefは#if defined(...)の短縮形です。
#errorは、予期しないコンパイル条件の際にエラーメッセージを出力し、コンパイルを停止させるために使用できます
3. コードブロックの一時的なコメントアウト
特定のコードブロックを一時的にコンパイル対象から外したいが、削除はしたくない場合、#if 0と#endifで囲む手法がよく使われます。
void complex_feature(void) {
// この機能を一時的に無効にする
#if 0
// ここから #endif までのコードはコンパイルされません
// 大量のコードや複数行のコメントアウトに便利です
setup_peripheral_A();
while(1) {
process_data_A();
}
#endif
// 代替処理など
setup_peripheral_B();
}
3.2 変数
3.2.1 データ型
C言語の主なデータ型は、
整数型には int、short、long、long long などがあり、符号なしを表す unsigned 修飾子も使えます。
浮動小数点型には float(単精度)と double(倍精度)があり、文字型は char です。
浮動小数点型には float(単精度)と double(倍精度)があり、文字型は char です。
整数型
• int: 符号付き整数型。システムによってサイズは異なりますが、一般的に4バイトです。
• short: 符号付きの短い整数型。通常は2バイトです。
• long: 符号付きの長い整数型。通常は4バイトですが、long long が使われる環境もある。
• long long: さらに大きな整数を扱います。通常は8バイトです。
• unsigned 修飾子: unsigned short、unsigned int、unsigned long のように指定することで、負の数を扱わず、より大きな正の数を格納できる。
• short: 符号付きの短い整数型。通常は2バイトです。
• long: 符号付きの長い整数型。通常は4バイトですが、long long が使われる環境もある。
• long long: さらに大きな整数を扱います。通常は8バイトです。
• unsigned 修飾子: unsigned short、unsigned int、unsigned long のように指定することで、負の数を扱わず、より大きな正の数を格納できる。
浮動小数点型
• float: 単精度浮動小数点型。小数点数値を扱う。
• double: 倍精度浮動小数点型。float よりも広い範囲と高い精度で数値を扱う。
• double: 倍精度浮動小数点型。float よりも広い範囲と高い精度で数値を扱う。
文字型
• char: 1バイトで、通常は文字コード(ASCIIなど)を格納する。
• signed char / unsigned char: char に符号の有無を指定する。
• signed char / unsigned char: char に符号の有無を指定する。
その他の型
• 配列: 同じデータ型の変数を連続して並べたものです。
例えば、char を複数つなげた char[] は文字列として扱われる。
• ポインタ: 変数が格納されているメモリ上のアドレスを格納する。
* を付けて宣言する。
例えば、char を複数つなげた char[] は文字列として扱われる。
• ポインタ: 変数が格納されているメモリ上のアドレスを格納する。
* を付けて宣言する。
3.2.2 符号有無での代入方法
符号付き64ビット整数
int64_t value_signed = 0x12345678abcdef01LL;符号なし64ビット整数
uint64_t value_unsigned = 0x12345678abcdef01ULL;3.2.3 ++iとi++の違い
C言語における++i(前置インクリメント)とi++(後置インクリメント)の主な違いは、変数の値がインクリメントされるタイミングと、式全体の評価値です。
どちらも最終的に変数iの値を1だけ増加させるという点では同じですが、その動作は異なる。
まず、変数iの値が1増加します。
次に、増加後の新しい値が式全体の値として使用(評価)される。
例:
まず、変数iの元の値が式全体(代入先の変数など)で使用(評価)される。
次に(式の評価後)、変数iの値が1増加する。
例:
ループ処理のfor (int i = 0; i < 10; ++i)のように、式の中で戻り値を使用しない場合は、通常どちらを使っても結果は同じです。
しかし、C++では一般的に前置インクリメントの方が効率が良い(余分なコピーが発生しない)とされるため、理由がなければ++iの使用が推奨されることがある。
より詳細な動作例は、東京情報大学のウェブサイト
で確認できる。
どちらも最終的に変数iの値を1だけ増加させるという点では同じですが、その動作は異なる。
++i (前置インクリメント)
動作順序:まず、変数iの値が1増加します。
次に、増加後の新しい値が式全体の値として使用(評価)される。
例:
int i = 5; int j = ++i; // i は 6 になる // j も 6 になる (増加後の値が代入される) コードは注意してご使用ください。
i++ (後置インクリメント)
動作順序:まず、変数iの元の値が式全体(代入先の変数など)で使用(評価)される。
次に(式の評価後)、変数iの値が1増加する。
例:
int i = 5; int j = i++; // j は 5 になる (元の値が代入される) // その後、i は 6 になる コードは注意して使用ください。
まとめ
| 特徴 | ++i (前置) | i++ (後置) |
| インクリメントのタイミング | 値の使用前 | 値の使用後 |
| 式全体の評価値 | インクリメント後の値 | インクリメント前の値 |
しかし、C++では一般的に前置インクリメントの方が効率が良い(余分なコピーが発生しない)とされるため、理由がなければ++iの使用が推奨されることがある。
より詳細な動作例は、東京情報大学のウェブサイト
で確認できる。
3.2.4 bit演算
論理演算には AND (&)、OR (|)、XOR (^)、NOT (~) があり、それぞれ桁ごとに2つのビットを比較して結果を返す。
シフト演算には左シフト (<<) と右シフト (>>) があり、ビット列を左右に移動させる。
特定のビットが立っているか確認するのに使われます。
• OR (|): 対応するビットの少なくとも一方が1の場合、結果のビットが1になる。
複数のビットを同時に1にしたい場合に便利です。
• XOR (^): 対応するビットが異なっている場合のみ、結果のビットが1になる。
特定のビットを反転させるのに使われます。
• NOT (~): ビットの0と1をすべて反転させます。
(32ビットなど、環境によって桁数は変わります)
右端には0が追加され、左端からあふれたビットは消えます。
これは元の数に2のN乗を掛け算することと等価です。
• 右シフト (>>): ビットを指定された数だけ右にずらします。
左端には0が追加されます。
元の数を2のN乗で割ることに等価です。
• 高速な掛け算・割り算: シフト演算は、2の累乗による掛け算や割り算を高速に行うために使われます。
シフト演算には左シフト (<<) と右シフト (>>) があり、ビット列を左右に移動させる。
ビット単位の論理演算
• AND (&): 対応するビットが両方とも1の場合のみ、結果のビットが1になる。特定のビットが立っているか確認するのに使われます。
• OR (|): 対応するビットの少なくとも一方が1の場合、結果のビットが1になる。
複数のビットを同時に1にしたい場合に便利です。
• XOR (^): 対応するビットが異なっている場合のみ、結果のビットが1になる。
特定のビットを反転させるのに使われます。
• NOT (~): ビットの0と1をすべて反転させます。
(32ビットなど、環境によって桁数は変わります)
ビット単位のシフト演算
• 左シフト (<<): ビットを指定された数だけ左にずらします。右端には0が追加され、左端からあふれたビットは消えます。
これは元の数に2のN乗を掛け算することと等価です。
• 右シフト (>>): ビットを指定された数だけ右にずらします。
左端には0が追加されます。
元の数を2のN乗で割ることに等価です。
具体的な使用例
• 特定のビットを抽出: 特定のビットを1にするには、元の数とビットマスクのOR演算を使います。• 高速な掛け算・割り算: シフト演算は、2の累乗による掛け算や割り算を高速に行うために使われます。
3.2.5 uintで違い
UINT64_C UINT64_MAX UINT_FAST64_MAX UINT_LEAST64_MAX uint_least64_t uint64_t uint_fast64_t u_int64_t 何が違うのか
C言語において、これらは全て64ビットの符号なし整数を扱うためのものですが、それぞれ用途と規格上の保証が異なります。
主な違いは以下の通りです。これらは通常、ヘッダーファイル <stdint.h> (C言語) または <cstdint> (C++) をインクルードして使用します。
• uint_least64_t や uint_fast64_t は、最低限の範囲またはパフォーマンスを重視する場合に選択されます。
• UINT*_MAX 系は型の最大値を示すマクロ、UINT64_C() は定数リテラルを適切な型にするためのマクロです。
C言語において、これらは全て64ビットの符号なし整数を扱うためのものですが、それぞれ用途と規格上の保証が異なります。
主な違いは以下の通りです。これらは通常、ヘッダーファイル <stdint.h> (C言語) または <cstdint> (C++) をインクルードして使用します。
型名 (typedef)
| 型名 | 用途 | 規格上の保証 |
|---|---|---|
| uint64_t | 厳密に64ビットの符号なし整数型が必要な場合に使用します。最も一般的です。 | 符号なしで正確に64ビット幅 (8バイト) であることが保証されています。 |
| uint_least64_t | 少なくとも64ビットの幅を持つ、最も小さい符号なし整数型が必要な場合に使用します。 | 幅は64ビット以上であることが保証されます。 |
| uint_fast64_t | 少なくとも64ビットの幅を持ち、その処理系で最も高速に処理できる符号なし整数型が必要な場合に使用します。 | uint_least64_t よりも広い幅になる可能性もありますが、速度が優先されます。 |
| u_int64_t | 標準C言語の規格には含まれておらず、主にBSD系などの一部のシステムで慣習的に使われる型名です。移植性を重視する場合は使用を避けるべきです。 | 処理系依存です。 |
マクロ (定数)
| マクロ | 用途 | 説明 |
|---|---|---|
| UINT64_C(c) | 整数定数に適切な型サフィックスを付与するために使用します。 | コンパイラやシステムに応じて、指定した数値 c を unsigned long long など、適切な64ビット符号なし整数型として扱わせます (例: UINT64_C(123) は 123ULL のように展開されます)。 |
| UINT64_MAX | uint64_t 型で表現可能な最大値を表す定数です。 | その値は常に 18,446,744,073,709,551,615 (2^64 - 1) となります。 |
| UINT_FAST64_MAX | uint_fast64_t 型で表現可能な最大値を表す定数です。 | 処理系定義の値になりますが、UINT64_MAX 以上です。 |
| UINT_LEAST64_MAX | uint_least64_t 型で表現可能な最大値を表す定数です。 | 処理系定義の値になりますが、UINT64_MAX 以上です。 |
まとめ
• uint64_t は、常に64ビットであることが保証されるため、クロスプラットフォームでのデータ交換や厳密なビット幅が必要な場面で最適です。• uint_least64_t や uint_fast64_t は、最低限の範囲またはパフォーマンスを重視する場合に選択されます。
• UINT*_MAX 系は型の最大値を示すマクロ、UINT64_C() は定数リテラルを適切な型にするためのマクロです。
3.2.6 8bit−>16bit変換
符号無し8bit変数を16bitの符号付き小数点の変数に変換
2つのuint8_t変数を1つの符号付き整数に結合し、それを float または double 型の小数点変数へ変換して1/16する文法は以下の通りです。
エンディアン(バイト順序)に注意すること。
上記のコードはビッグエンディアンを想定している。
• double型へのキャストは、浮動小数点演算を保証するために重要です。
2つのuint8_t変数を1つの符号付き整数に結合し、それを float または double 型の小数点変数へ変換して1/16する文法は以下の通りです。
#include <stdint.h>
#include <stdio.h>
int main() {
uint8_t byte1 = 0x12; // 例: 上位バイト
uint8_t byte2 = 0x34; // 例: 下位バイト
// 2バイトを16ビット符号付き整数 (int16_t) に結合・変換
// ビッグエンディアンの場合:
int16_t signed_value = (int16_t)((byte1 << 8) | byte2);
// 符号付き小数点変数へ変換し 1/16 する
double result = (double)signed_value / 16.0;
printf("符号付き十進数: %d\n", signed_value);
printf("計算結果: %f\n", result);
return 0;
}
• signed_valueに結合する際は、ビットシフトと論理和を使用する。エンディアン(バイト順序)に注意すること。
上記のコードはビッグエンディアンを想定している。
• double型へのキャストは、浮動小数点演算を保証するために重要です。
3.2.7 64bit整数->8bit配列
ポインタを利用する方法とビットシフト演算を利用する方法がある。
いずれの方法も、システムの**バイトオーダ(エンディアン)**に注意する必要がある。
この方法は簡潔ですが、システムのバイトオーダ(リトルエンディアンかビッグエンディアンか)に依存する。
この方法はバイトオーダを意識して明示的に格納順序を制御できるため、異なるCPUアーキテクチャ間でのデータ交換(ネットワーク通信など)において移植性が高くなります。
以下の例は、最上位バイトから順に配列に格納する(ビッグエンディアン形式で格納する)場合のコードです。
いずれの方法も、システムの**バイトオーダ(エンディアン)**に注意する必要がある。
方法1: ポインタとキャストを利用する (処理系依存)
64ビット整数型 (int64_t または long long) へのポインタを1バイトの符号なし整数型 (unsigned char) へのポインタにキャストすることで、 メモリ上のバイト列として直接アクセスできます。この方法は簡潔ですが、システムのバイトオーダ(リトルエンディアンかビッグエンディアンか)に依存する。
#include <stdio.h>
#include <stdint.h> // int64_t, uint8_t を使用するために必要
int main(void) {
int64_t large_int = 0x1122334455667788LL;
unsigned char byte_array[8];
// ポインタを unsigned char* にキャストしてバイト配列として扱う
unsigned char *ptr = (unsigned char *)&large_int;
// バイト配列にコピー
for (int i = 0; i < 8; i++) {
byte_array[i] = ptr[i];
}
// 結果の表示(バイトオーダによって出力順序が変わる)
printf("Original int64_t: 0x%llx\n", large_int);
printf("Byte array: ");
for (int i = 0; i < 8; i++) {
printf("0x%02x ", byte_array[i]);
}
printf("\n");
return 0;
}
方法2: ビットシフト演算を利用する (移植性が高い)
ビットシフト演算 ( >> や & ) を使用して各バイトを抽出し、配列に格納する方法です。この方法はバイトオーダを意識して明示的に格納順序を制御できるため、異なるCPUアーキテクチャ間でのデータ交換(ネットワーク通信など)において移植性が高くなります。
以下の例は、最上位バイトから順に配列に格納する(ビッグエンディアン形式で格納する)場合のコードです。
#include <stdio.h>
#include <stdint.h>
int main(void) {
int64_t large_int = 0x1122334455667788LL;
unsigned char byte_array[8];
// ビッグエンディアン形式でバイト配列に格納
byte_array[0] = (unsigned char)((large_int >> 56) & 0xFF);
byte_array[1] = (unsigned char)((large_int >> 48) & 0xFF);
byte_array[2] = (unsigned char)((large_int >> 40) & 0xFF);
byte_array[3] = (unsigned char)((large_int >> 32) & 0xFF);
byte_array[4] = (unsigned char)((large_int >> 24) & 0xFF);
byte_array[5] = (unsigned char)((large_int >> 16) & 0xFF);
byte_array[6] = (unsigned char)((large_int >> 8) & 0xFF);
byte_array[7] = (unsigned char)(large_int & 0xFF);
// 結果の表示
printf("Original int64_t: 0x%llx\n", large_int);
printf("Byte array (Big Endian): ");
for (int i = 0; i < 8; i++) {
printf("0x%02x ", byte_array[i]);
}
printf("\n");
return 0;
}
データ通信などでバイトオーダの統一が必要な場合は、方法2のように明示的なビットシフト演算で順序を規定することをお勧めする。
3.2.8 複数bitの一部書き換え
64bit int で特定のbitを読み取り、書き換えする方法
C言語で64bit整数(long long型や標準ライブラリのinttypes.h、stdint.hで定義されているint64_t, uint64_t型)の特定のビットにアクセスするには、 ビット演算子(シフト << , >>、論理積 &、論理和 | 、排他的論理和 ^ 、ビット反転 ~ )を使用する。
以下に、x bit目(0から63の範囲)のデータを取得・修正する方法を示す。
符号付き整数 (int64_t など) では、右シフト >> の動作が環境によって異なる場合があるためです。
• ビット番号: ここでは最下位ビットを0とし、最上位ビットを 63 とします。
目的のビット位置に1を立てたマスクを作成し、元の数値と論理積をとります。
結果が0であればそのビットは0、0でなければそのビットは1です。
これにより、対象のビット位置のみが0になり、他は変更されません。
これにより、対象のビットが0なら1に、1なら0に反転します。
C言語で64bit整数(long long型や標準ライブラリのinttypes.h、stdint.hで定義されているint64_t, uint64_t型)の特定のビットにアクセスするには、 ビット演算子(シフト << , >>、論理積 &、論理和 | 、排他的論理和 ^ 、ビット反転 ~ )を使用する。
以下に、x bit目(0から63の範囲)のデータを取得・修正する方法を示す。
前提知識
• 型: 移植性を考慮し、stdint.h をインクルードして uint64_t (符号なし64bit整数) を使用することを推奨します。符号付き整数 (int64_t など) では、右シフト >> の動作が環境によって異なる場合があるためです。
• ビット番号: ここでは最下位ビットを0とし、最上位ビットを 63 とします。
#include <stdint.h> // uint64_t を使用するために必要 #include <stdio.h>
1. 該当する x bit目のデータ取得
特定のビットが 0 か 1 かを判定するには、左シフト演算子 (<<) と論理積演算子 (&) を使います。目的のビット位置に1を立てたマスクを作成し、元の数値と論理積をとります。
結果が0であればそのビットは0、0でなければそのビットは1です。
// data: 対象の64bit整数
// x: 取得したいビットの位置 (0-63)
uint64_t get_bit(uint64_t data, int x) {
// 1をxビット左シフトしてマスクを作成 (例: x=3なら 0...01000)
uint64_t mask = (uint64_t)1 << x;
// 論理積をとる
if (data & mask) {
return 1; // ビットは1
} else {
return 0; // ビットは0
}
}
または、右シフトして最下位ビットを確認する方法もあります。
uint64_t get_bit_alt(uint64_t data, int x) {
// xビット右シフトして、最下位ビットをマスクで取り出す
return (data >> x) & 1;
}
2. 該当する x bit目のデータ修正(セット/クリア/反転)
ビットを修正するには、論理和 (|)、ビット反転 (~)、論理積 (&)、排他的論理和 (^) を組み合わせて使用します。a. x bit目を 1 にセットする (Set)
対象のビット位置に1を立てたマスクと、元の数値の論理和をとります。
// dataのx bit目を1に設定する
uint64_t set_bit(uint64_t data, int x) {
uint64_t mask = (uint64_t)1 << x;
return data | mask;
}
b. x bit目を 0 にクリアする (Clear / Reset)
対象のビット位置に1を立てたマスクをビット反転 (~) させ、元の数値と論理積をとります。これにより、対象のビット位置のみが0になり、他は変更されません。
// dataのx bit目を0にクリアする
uint64_t clear_bit(uint64_t data, int x) {
uint64_t mask = (uint64_t)1 << x;
return data & (~mask);
}
c. x bit目を反転させる (Toggle)
対象のビット位置に1を立てたマスクと、元の数値の排他的論理和 (^) をとります。これにより、対象のビットが0なら1に、1なら0に反転します。
// dataのx bit目を反転させる
uint64_t toggle_bit(uint64_t data, int x) {
uint64_t mask = (uint64_t)1 << x;
return data ^ mask;
}
3.3 配列
3.3.1 宣言と入力
配列変数について 宣言 以外の場所で変数に数値を入れれないのはなぜ
C言語では、配列変数に宣言以外の場所(宣言後)でまとめて数値を入れられないのは、 配列名が配列全体の記憶域を指すポインタ定数として扱われるため、代入演算子(=)の左辺値として使えないからです。
詳細な理由は以下の通りです。
1. 配列名とポインタの関係
C言語において、配列を宣言すると、コンパイラはその配列の最初の要素のアドレス(先頭アドレス)を変数名として関連付けます。この配列名は、アドレス自体は変更できないポインタ定数として扱われます。
2. 代入演算子の制約
代入演算子 = は、左辺に「書き換え可能な記憶領域」、すなわち「左辺値 (lvalue)」を必要とする。しかし、配列名はアドレスが固定されたポインタ定数であるため、その値を変更することはできない。
そのため、宣言時以外に配列名全体に対して一度に値を代入しようとすると、コンパイルエラーになる。
例外:宣言時の初期化
宣言時(定義時)は特別で、コンパイラがメモリを確保し、指定された初期値でその領域を埋めるという一連の処理を自動的に行ってくれる。これは「代入」ではなく「初期化」と呼ばれる異なる処理です。
int a[5] = {1, 2, 3, 4, 5}; // OK: 初期化
3. 宣言後に値を設定する方法
宣言後に配列の各要素に値を設定したい場合は、以下のいずれかの方法を用いる必要がある。• 要素ごとに代入する: ループ処理などを使って、各要素に個別に値を代入する。
int a[5]; // 宣言 a[0] = 1; // OK: 要素ごとの代入 a[1] = 2; // ...• 文字列操作関数を使う (文字配列の場合): strcpy などの標準ライブラリ関数を使用します。
これらの関数は、内部でループ処理を行い、指定された文字列をメモリ領域にコピーしてくれる。
#include <string.h> char s[20]; strcpy(s, "Hello"); // OK: 文字列コピー関数を使用• 構造体として扱う (C99以降): 配列を構造体のメンバーとして宣言すれば、その構造体変数全体を代入することは可能です。
これらの制限は、C言語がメモリ管理を厳密に行う設計思想に基づいているためです。
これにより、意図しないメモリ上書きなどのバグを防ぐ役割も果たしている。
3.3.2 配列とポインター
uint8_t arr[5][2] ={{0x10,0x01},{0x20,0x02},{0x30,0x03},{0x40,0x04},{0x50,0x05}};
uint32_t *p;
p = &arr[2][1];
*p =0x12243453;
配列の内容はどうなりますか
初期状態: arr[2][1]のアドレスは、配列arrの中の0x03, 0x04, 0x05, 0x04, ...と続くメモリ領域を指している。
uint32_t *p;
p = &arr[2][1];
*p =0x12243453;
配列の内容はどうなりますか
初期状態: arr[2][1]のアドレスは、配列arrの中の0x03, 0x04, 0x05, 0x04, ...と続くメモリ領域を指している。
• ポインタの型変換と代入
• pはuint32_t*型(4バイトを一度に読み書きする)です。
• p = &arr[2][1];により、pはarr[2][1]の場所から始まるメモリ領域を指します。
• *p = 0x12243453;により、このアドレスから連続する4バイトに0x12243453という値が書き込まれます。
• リトルエンディアンでの格納: uint32_tの0x12243453は、リトルエンディアンではメモリ上に**0x53, 0x34, 0x24, 0x12**の順で格納されます。
• 結果: 元のuint8_tの配列のメモリ配置に重ね合わせると、arr[2][1]からarr[4][0]までの4つの要素が順に0x53, 0x34, 0x24, 0x12に上書きされます。
[0][0]=0x10
[0][1]=0x01
[1][0]=0x20
[1][1]=0x02
[2][0]=0x30
[2][1]=0x53⬅変更
[3][0]=0x34⬅変更
[3][1]=0x24⬅変更
[4][0]=0x12⬅変更
[4][1]=0x05
• pはuint32_t*型(4バイトを一度に読み書きする)です。
• p = &arr[2][1];により、pはarr[2][1]の場所から始まるメモリ領域を指します。
• *p = 0x12243453;により、このアドレスから連続する4バイトに0x12243453という値が書き込まれます。
• リトルエンディアンでの格納: uint32_tの0x12243453は、リトルエンディアンではメモリ上に**0x53, 0x34, 0x24, 0x12**の順で格納されます。
• 結果: 元のuint8_tの配列のメモリ配置に重ね合わせると、arr[2][1]からarr[4][0]までの4つの要素が順に0x53, 0x34, 0x24, 0x12に上書きされます。
[0][0]=0x10
[0][1]=0x01
[1][0]=0x20
[1][1]=0x02
[2][0]=0x30
[2][1]=0x53⬅変更
[3][0]=0x34⬅変更
[3][1]=0x24⬅変更
[4][0]=0x12⬅変更
[4][1]=0x05
こんな感じでModBusの記憶を配列で処理すればよいのでは。
最初の0〜4がアドレスに対応
3.3.3
2次元配列の1次部分を1次配列に代入するには
memcpy関数を使うか、forループで要素を一つずつコピーします。memcpy関数を使用する方法
memcpy関数を使うと、メモリブロックを一度にコピーできます。配列の1次部分のアドレスとサイズを指定してコピーします。
#include <stdio.h>
#include <string.h>
int main() {
int array2D[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
int array1D[4];
// 0行目のデータをarray1Dにコピーする
memcpy(array1D, array2D[0], sizeof(int) * 4);
for (int i = 0; i < 4; i++) {
printf("%d ", array1D[i]);
}
printf("\n");
return 0;
}
forループを使用する方法
forループを使って、元の2次元配列の要素を1つずつ1次配列にコピーする方法です。
#include <stdio.h>
int main() {
int array2D[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
int array1D[4];
// 0行目のデータをarray1Dにコピーする
for (int i = 0; i < 4; i++) {
array1D[i] = array2D[0][i];
}
for (int i = 0; i < 4; i++) {
printf("%d ", array1D[i]);
}
printf("\n");
return 0;
}
3.4 関数
3.4.1 書き方
関数の書き方(定義と呼び出し)は
特定の構文に従います。
関数は、処理をひとまとめにして再利用可能にするための重要な要素です。
関数の定義の基本構文
関数の定義は以下の形式で行う。
戻り値の型 関数名(引数の型1 仮引数名1, 引数の型2 仮引数名2, ...)
{
// 関数の本体(処理内容)
// 必要に応じて return 文で値を返す
}
コードは注意してご使用ください。
• 戻り値の型: 関数が処理後に返す値のデータ型を指定します。値を返さない場合は void を指定します。• 関数名: 関数を識別するための名前です。
• 引数 (ひきすう): 関数に渡す値とそのデータ型を指定します。引数がない場合は void または空の () を指定します。
• 仮引数名: 関数内部で引数として渡された値を使用するための変数名です。
• return 文: 戻り値がある場合、return キーワードを使って値を呼び出し元に返します。
サンプルコード
2つの整数を受け取り、その合計を返す関数の例です。
#include <stdio.h>
// main関数より前に定義する場合、プロトタイプ宣言は必須ではないが、
// 一般的にはmain関数の前にプロトタイプ宣言を行い、main関数の後ろに関数定義を記述するスタイルが推奨される。
// プロトタイプ宣言(関数名と引数・戻り値の型をコンパイラに知らせる)
int add(int a, int b);
int main(void)
{
int num1 = 10, num2 = 20, sum;
// 関数の呼び出し
sum = add(num1, num2);
printf("合計は %d です\\n", sum);
return 0;
}
// 関数の定義
int add(int a, int b)
{
int result;
result = a + b;
return result; // 結果を呼び出し元に返す
}
コードは注意してご使用ください。重要なポイント
•宣言と定義: C言語では、関数を使う前にその存在と型をコンパイラに知らせる「宣言(プロトタイプ宣言)」が必要です。main 関数よりも後に関数を定義する場合、main 関数の前にプロトタイプ宣言を記述する必要があります。
•main 関数: Cプログラムの実行は必ず main 関数から始まります。
•複数の戻り値: C言語の関数が return で直接返せる値は1つだけです。
複数の値を操作したい場合は、ポインタを使ってメモリのアドレスを渡す「参照渡し」などのテクニックが必要になります。
3.4.2 printf
1 16進数2桁を表示するには
printf関数でフォーマット指定子に%02xまたは%02Xを使用します。
%xは小文字、%Xは大文字で表示され、02は2桁に0埋めすることを指定します。
• 小文字で表示: printf("%02x", variable);
• 大文字で表示: printf("%02X", variable);
C言語で printf を使って小数点第2位まで表示するには、書式指定子 %
小数点以下を第2位で固定したい場合は、%.2f と指定する。
#include <stdio.h>
int main() {
double pi = 3.1415926535;
printf("%.2f\n", pi); // 小数点以下2桁で表示
return 0;
}
出力:3.14
解説
• %f: 浮動小数点数(double型またはfloat型)を表示するための書式指定子です。• .2: 小数点以下の桁数を2桁に指定します。 この指定により、小数点以下第3位で四捨五入され、第2位までの表示になります。
• printf("%.2f\n", pi); は、変数 pi の値を小数点以下第2位まで表示し、改行します。
2 64ビット整数を表示するには
long long int型を使用し、printfの書式指定子に%lld(10進数)または%llx(16進数)を使用します。64ビット整数の表示方法
• 10進数で表示する場合: %lld
• 16進数で表示する場合: %llx
コード例
#include <stdio.h>
int main() {
long long int my_num = 123456789012345LL;
// 10進数で表示
printf("10進数: %lld\n", my_num);
// 16進数で表示
printf("16進数: %llx\n", my_num);
return 0;
}
注意点
• 64ビット整数を格納するには、long long int型を使用する必要がある。• printf関数に渡す値と書式指定子を一致させる必要がある。
3.4.3 if
C言語のif文で使われる比較演算子は
等しい (==)
等しくない (!=)
大きい (>)
小さい (<)
以上 (>=)
以下 (<=)
の6つです。
これらは2つの値を比較し、条件が真か偽かを判定するために使われます。
等しい (==)
等しくない (!=)
大きい (>)
小さい (<)
以上 (>=)
以下 (<=)
の6つです。
これらは2つの値を比較し、条件が真か偽かを判定するために使われます。
| 演算子 | 一般的な読み方 | 意味 |
|---|---|---|
| == | イコール | 2つの値が等しい |
| != | ノットイコール | 2つの値が等しくない |
| > | 大なり | 左辺が右辺より大きい |
| < | 小なり | 左辺が右辺より小さい |
| >= | 大なりイコール | 左辺が右辺以上 |
| <= | 小なりイコール | 左辺が右辺以下 |
3.4.4 for、while、do-while
ループ(for、while、do-while)から抜けるために主に以下の2つの方法がある。
・break文が実行されると、その文が含まれている最も内側のループが即座に終了し、ループの直後の文に制御が移ります。
・ネストされた(入れ子になった)ループの場合、breakはその1階層しか抜けられません。
・ループ構造の外側にあるラベルにジャンプすることで、ループから抜けることができます。
・注意点: gotoはプログラムの構造を複雑にし、読みにくく、デバッグしにくくするため、通常は非推奨とされています。
特にネストされた複数のループを一気に抜けたい場合など、breakでは対応が難しい特定の状況でのみ限定的に使用されることがあります。
基本的には、可読性が高く安全な**break文**を使用することを推奨します。
1. break文
・最も一般的で標準的な方法です。・break文が実行されると、その文が含まれている最も内側のループが即座に終了し、ループの直後の文に制御が移ります。
・ネストされた(入れ子になった)ループの場合、breakはその1階層しか抜けられません。
#include <stdio.h>
int main() {
for (int i = 0; i < 10; i++) {
if (i == 5) {
break; // iが5の時にループを抜ける
}
printf("iの値: %d\n", i);
}
// ループを抜けた後の処理
printf("ループ終了\n");
return 0;
}
コードは注意してご使用ください。2. goto文
・「ジャンプ」による方法です。 ・goto文は、指定されたラベル(label_name:)の位置までプログラムの実行フローを強制的にジャンプさせます。・ループ構造の外側にあるラベルにジャンプすることで、ループから抜けることができます。
・注意点: gotoはプログラムの構造を複雑にし、読みにくく、デバッグしにくくするため、通常は非推奨とされています。
特にネストされた複数のループを一気に抜けたい場合など、breakでは対応が難しい特定の状況でのみ限定的に使用されることがあります。
#include <stdio.h>
int main() {
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
if (i * j > 6) {
printf("i*j が6を超えたためジャンプします。\n");
goto end_loop; // end_loopラベルへジャンプ
}
printf("i=%d, j=%d\n", i, j);
}
}
end_loop: // ジャンプ先のラベル
printf("二重ループ終了\n");
return 0;
}
コードは注意してご使用ください。| 方法 | 特徴 | 推奨度 |
|---|---|---|
| break | 最も内側のループを抜ける | 強く推奨 |
| goto | 指定した場所に強制ジャンプ | 非推奨(特殊な状況のみ) |
3.4.5 switch () case
これはswitch文の一部として、特定の値に対して条件分岐を行うための制御構文です。
caseはswitch文の中で、複数のcaseラベルから条件に合うものを実行する。
• その値がどのcaseラベルに一致するかを比較する。
• 一致するcaseが見つかると、そのラベルから始まるコードが実行される。
• 通常、caseの最後にbreak文を記述し、switch文から抜ける処理をする。
breakがないと、次のcaseラベルに進んでしまう点に注意が必要です。
caseはswitch文の中で、複数のcaseラベルから条件に合うものを実行する。
switch-case文の仕組み
• switch文の後に続く式(通常は整数型の変数)の値が評価される。• その値がどのcaseラベルに一致するかを比較する。
• 一致するcaseが見つかると、そのラベルから始まるコードが実行される。
• 通常、caseの最後にbreak文を記述し、switch文から抜ける処理をする。
breakがないと、次のcaseラベルに進んでしまう点に注意が必要です。
case文の例
#include <stdio.h>
int main() {
int score = 85;
switch (score / 10) {
case 10:
case 9:
printf("評価はAです。\n");
break;
case 8:
printf("評価はBです。\n");
break;
case 7:
printf("評価はCです。\n");
break;
default:
printf("評価はDです。\n");
}
return 0;
}
