C言語とは? Javaとの違いやポインタ変数について解説


皆さん初めまして。株式会社シー・エス・エスのヤマ男です。
先日C言語について学習する機会があったので、私自身の備忘録も兼ねて学んだ内容を紹介したいと思います。

本記事では特に私が苦戦したJavaとの違いについて注目して記載してみます。

1. 対象読者

このブログの内容は主に以下の方を対象読者としています。

  • C言語初学者
  • Java経験者(もしくは他言語経験者)
  • CとJavaの違いについて軽く知りたい人

2. C言語を学習するに至った経緯

2-1. C言語初学者

大学ではJavaとPythonを主に勉強し、社会人になって初配属の現場の使用言語もJavaだったため、これまでC言語については未経験同然でした。

2-2. 環境の変化

社会人2年目の途中で現場が変わることになりました。そしてその新しい現場ではなんと、JavaだけではなくC言語を用いた開発も行っていました。
JavaやPythonと共通している内容の範囲内であればあまり問題はありませんでしたが、「ポインタ」というものが一切分からず、当初非常に苦労をしました。

2-3. 学習する必要が生まれた

これまではC言語について、「ちょっと古めなプログラミング言語」「Javaと比べて難しそう」という認識程度しか持ち合わせていませんでした。
しかし、今回の件で今後もエンジニアとして働く上でこれまでの認識、知識のままではまずいかなと危機感を覚えました。
ということで、一度0からC言語を学んでみようと思ったのが今回の経緯です。

3. C言語とは

C言語を学習するにあたってまずはC言語に少しでも興味を持たなければと思い、
「C言語とはどんな言語なのか」について調べてみました。
(お恥ずかしながら興味が無いものに対してはどんなに勉強しても頭に入らない人間なので。。。)

3-1. C言語の特徴

  • 長寿

1970年代に作られたUNIX(OS)を様々な機種に移植できるよう開発されたのがC言語です。
アセンブリ言語とは異なり、人間が理解しやすい高級言語で、コンピュータ制御からシステム開発まで幅広い分野で長年使用されている汎用性の高い現役のプログラミング言語です。

  • 汎用性が高い

C言語は他言語(Java等)と同じくシステム開発でも利用出来る他、メモリ管理やポインター演算等、ハードウェアの制御にも向いている言語です。
また、OSに依存しないという点も特徴の一つです。
幅広い分野で活用でき、環境にも依存しないため、C言語は様々な業界から長年重宝され続けているプログラミング言語であると言えます。

  • 実行速度が早い

C言語はコンパイル方式を利用しているのと、効率よく機械語へ翻訳できるよう設計されているため、他言語と比べ処理速度が早いのが特徴です。

  • 難しい

現役のプログラミング言語の母的な存在なC言語ですが、コンピュータ制御が可能な分、Java等では意識しなくても良い部分も制御出来てしまうため習得が難しめな言語であるといわれています。
ただ、C言語の文法自体は簡潔で、他言語もC言語から影響を受けている箇所があるため、C言語を習得したら他言語の習得も幾分か楽になるみたいです。

3-2. Tips

「C++」と「C#」

C言語というと「C」の他に「C++」と「C#」があります。
それぞれの特徴については以下の通りです。

  • C++

C言語にオブジェクト指向機能を拡張した上位互換の言語。ただ、出来ることが広がった代わりに仕様が複雑になってしまっている。

  • C#

C++から派生して生まれたオブジェクト指向言語。Javaに文法が似ている。マイクロソフト社が開発した言語でVisual Studioが使用できるため、GUIアプリの開発に向いている。
ーーー
名前が似ているというと、JavaとJavaScriptを思い浮かべますが、これらとは違ってそれぞれ繋がりはあるようです。

「C言語」の前身

C言語の名前の由来は「B言語」の次に作られた言語であるためです。
※ちなみにB言語の前に「A言語」があると言う訳ではなく、BCPLというプログラミング言語を元にしたからだそうです。
ーーー
「D言語」というC言語の後継言語も開発されているみたいです。


4. Javaとの違い

さて、ここまで長くなってしまいましたが、いよいよ本題です。
以下に私が勉強をしていて、「特にJavaと違う」と思ったところについて重要度(※個人的偏見)も併せて紹介していきます。

4-1. ポインタ (ポインタ変数) (重要度:★★★)

C言語ではアドレスをデータとして扱います。
そのアドレスを保持する変数が「ポインタ変数」です。

では、次に「ポインタ変数」をどう使うのかという点ですが、
以下変数について理解していただけると分かりやすいかと思います。
 ・名前 (例:num, hoge)
 ・値 (例:10, 'a')
 ・アドレス (例:0x7aaf060ebb68)
ソースコード上の変数とは上記3種類の情報を持っています。
「アドレスって何?」となる方はコインロッカーを思い浮かべてもらうと分かりやすいかもしれません。

int num = 10;
char hoge = 'a';

上記変数宣言では、「num」という箱に値「10」を入れて、メモリ上(例:コインロッカー)のどこかへ保持しておくということをしています。
Javaで開発しているとメモリ上の在り処について意識することはありませんが、C言語では
ポインタ変数」というものを使って、変数numの値を直接参照・操作することができるのです。

int num = 10;
char hoge = 'a';
/* ポインタ変数宣言 */
int *p_num = # // 変数numのアドレスを取得
char *p_hoge = &hoge; // 変数hogeのアドレスを取得

ポインタ変数の宣言時にはポインタ変数であることを示す「*」を変数名の前に付けます。
変数のアドレスは変数の前に「&」を付けて変数参照することで取得する事が出来ます。

これによるメリットは以下があります。
呼び出した関数から2つ以上の値を返してもらえる。(アドレス渡し)
 →return文では一つしか値を返せないが、ポインタ変数を引数に渡してあげることで、
  アドレス先の値を直接更新してもらうことで、値を複数操作できる。
  (データ受け渡し用のクラス(=DTO)を用意すればJavaでも複数値受け取ることは可能)

構造体のデータをポインタ変数1つで取得可能。
 →構造体のアドレスを取得して、「ポインタ変数->メンバ」とアローダイアグラムを使用するこ
  とで、構造体内部のメンバを取得する事が出来る。

/* 構造体の宣言 */
typedef struct {
    int num;
    char alphabet;
} Sample;

void main(void) {
    Sample sample = {10, 'a'};
    Sample *p_sample = &sample; // 構造体のアドレスを取得
    printf("sampleのnumは%dです。", p_sample->num); 
}

/* 結果 */
sampleのnumは10です。


関数ポインタを使用することで呼び出す関数を動的に実装することが可能。
 →関数が格納されているアドレスをポインタ変数へ代入することで、配列の要素に関数を
  埋め込むことが出来る。

#include <stdio.h>

int add(int num1, int num2) {
    return num1 + num2;
}

int multi(int num1, int num2) {
    return num1 * num2;
}

/* 関数ポインタの定義 */
int (* p_FuncArray[2] ) ( int, int ) = { &add, &multi };

void main(void) {
    // add呼び出し
    printf("add:%d\n", p_FuncArray[0](2, 5) );

    // multi呼び出し
    printf("multi:%d", p_FuncArray[1](2, 5) );
}

/* 結果 */
add:7
multi:10

4-2. プロトタイプ宣言 (重要度:★★☆)

Javaでは関数を定義したらそのまま使用(=コンパイル)出来ましたが、
C言語ではコードの上から順に読み込むため、使用箇所よりも後ろに定義されているとコンパイルエラーが起きてしまいます。

#include <stdio.h>

void main(void) {
    // add呼び出し
    int ans = add(2, 5);
    printf("add:%d\n", ans );
}

int add(int num1, int num2) {
    return num1 + num2;
}

/* 結果 */
コンパイルエラーが起きる

そのためC言語では「プロトタイプ宣言」といって、
ソースコードの冒頭部分で関数の宣言だけ行い、定義は後でする必要があります。
 →同ファイル内にプロトタイプ宣言も可能だが、ヘッダファイル(.h)に宣言してincludeする
  方式の方をよく見かける。

#include <stdio.h>

int add(int, int); // プロトタイプ宣言

void main(void) {
    // add呼び出し
    int ans = add(2, 5);
    printf("add:%d\n", ans );
}

int add(int num1, int num2) {
    return num1 + num2;
}

/* 結果 */
コンパイルできる。


#ifndef SAMPLE_H
#define SAMPLE_H
 
int add(int num); //プロトタイプ宣言
 
#endif // HOGE_H__

#include <stdio.h>
#include "sample.h" // ヘッダファイルをicludeする

void main(void) {
    // add呼び出し
    int ans = add(2, 5);
    printf("add:%d\n", ans );
}

int add(int num1, int num2) {
    return num1 + num2;
}

/* 結果 */
コンパイルできる。

4-3. 引数なし関数には()内にvoidを記述する (重要度:★★★)

下記二つの関数ではコンパイル時の挙動が違います。

void func1() {} 
void func2(void) {} 

上記のfunc1()はエラーにならず、コンパイルが出来てしまいますが、
func2()はコンパイルエラーとなります。
挙動が変わってしまうのは以前のC言語が標準化される前の宣言方法が残されているためらしいです。

JPCERT/CCのWebページにも上記は記載されており、情報セキュリティインシデントにも繋がりかねないため、
引数なし関数にはしっかり「void」を記載する必要があります。

4-4. 文字列型(=String)が無い (重要度:★☆☆)

Javaではお馴染みのString型がC言語にはありません。
C言語では文字列を「1文字が連続したもの」として、char型の配列で文字列を扱います。

また、C言語では文字列をの終わりを「\0」で認識するため、配列を宣言する場合は「文字数+1」で宣言する必要があります。

str[] = "abcd"; // 初期値を設定する場合、要素数は設定しなくても大丈夫。
str[5] = "abcd"; // 'a', 'b', 'c', 'd', '\0'


5. 【最後に】今後もC言語を学び続ける必要がある

今回C言語について学んでみて、Javaとの違いについて知ることが出来ました。
また、C言語に触れて一番「?」となってしまった「ポインタ」について知識を深められる良い機会となりました。とはいえ、C言語についての理解はまだまだ浅いですし、何より経験が足りないので今後も学び続ける必要がありますね。。

この記事が少しでも私と同じC言語初学者の助けになれば幸いです。m(_ _)m
最後まで読んでいただきありがとうございました!!

\システム開発なら、株式会社シー・エス・エスへ/

6. 参考記事

C言語とは|特徴やC++やC#との違いを分かりやすく解説
一週間で身につくC言語の基本
【C言語入門】ポインタのわかりやすい使い方(配列、関数、構造体)
DCL20-C. 引数を受け付けない関数の場合も必ず void を指定する

7. この記事を書いた人


【ニックネーム】:ヤマ男
【経歴】:入社3年目。情報系の大学出身で主にJavaの現場に携わってきました。
【好きなもの】:家、ゲーム(FPS, FFシリーズ)、コーヒー☕(できれば甘いカフェラテ)