ジャンル雑多なゲーム・ゲーム制作関連の色々な情報を取り扱っているブログ。最近はBlenderについてが中心。
[C, C++学習]UE4触ってみたのと、C, C++言語再学習ノート-2日目- –C言語の配列、変数のスコープ、記憶領域の話、記憶クラス指定子によるスコープの範囲変更

[C, C++学習]UE4触ってみたのと、C, C++言語再学習ノート-2日目- –C言語の配列、変数のスコープ、記憶領域の話、記憶クラス指定子によるスコープの範囲変更

UE4でビューモードやビューポート操作、 C言語の配列と、変数のスコープ(有効範囲)、コンピューターの記憶領域の種類と、記憶領域の場所によってスコープの範囲が変えられるというお話 —勉強しなおしの記録というか学習ノート-2日目-

Unreal Engin4すごい!(語彙力)ってなったので、少しいじりつつ、むかーしかじった事あるC,C++の学びなおしの学習ノート。この連載記事の詳しい趣旨と注意事項は1日目をご覧ください。

※2019年5月25日 加筆・修正を行いました。

目次

UE4の方で、オブジェクト操作や、ビューポート操作してみたり

レベルエディタで、私がUE4公式ドキュメントで公開されているレベルデザイナ向けクイックスタートを参考に昨日作ってみたレベルを晒しつつ、ビューモードやビューポートの操作方法をgifと共にメモ的に載せてみます。

操作方法は、最近プレビュー公開され始めたUnreal Engin学習ポータルがあったので、それに登録して学ばせていただいてます。

ビューモードを選択して、オブジェクトの見え方を変える

UE4でビューモード選択するgif

ビューポートを3つにして表示

UE4でビューポートレイアウトを変更して3つのビューポート表示するgif

「かっこいい」とかも思ったのですが、それだけじゃなく、多角からの視点があることで、オブジェクト配置がより効率よく進められそうですね。「かっこいい」(2回目)

UE4で3つのビューポート表示したときにサブのビューポートの視点を操作する画面スクリーンショット

マウス右ボタンによるドラッグで、見る位置を変えられます。

UE4のビューポート3つ表示した時にサブのビューポートでオブジェクト選択する画面スクリーンショット

↑サブのビューポート上で(右ボタンでも左ボタンでも)選択したオブジェクトは、↓メインのビューポート上で選択したのと同じ扱い。

C, C++学習ノート

昨日飛ばしてしまったのですが、配列について

そこまで変わらないのですが、配列の記述についてさらっと触れます。

  • 宣言と初期化を同時に行う場合
int a[] = { 3, 5, 11 };
int b[5] = {1, 2, 3, 4, 5};

配列名の横の[]は、省略しちゃいけません。その中に、配列に格納する要素数を添え字としてセットすることはできます。

  • 宣言の後、それぞれの配列の要素数をを指定して別々に値を格納する場合
float b[5];
b[0] = 0.1;
b[1] = 0.2;
b[2] = 0.3;
b[3] = 0.4;
b[4] = 0.5;

※ただし、配列の宣言と初期化を同時にしない場合には、[]の中の数を省略できません。下のようなことをするとエラーになります。

float b[]; // エラーするコード
b[0] = 0.1;
b[1] = 0.2;
b[2] = 0.3;
b[3] = 0.4;
b[4] = 0.5;

配列に格納されている値を取り出すときは同じようにできます。

printf(b[3]); // 実行結果:0.4

参考にさせていただいた外部ページ:一週間で身につくC言語の基本|第5日目

変数の有効範囲(スコープ)

JavaScriptのvarと同じかなと思うのですが、一応。

#include <stdio.h>

// グローバル変数
int a = 1;

// プロトタイプ宣言
void func1();

void main(){
    printf("%d\n", a);
    printf("%d\n", b); // エラーするコード
}

void func1(){
    // ローカル変数
    int b = 3;
}

変数は、生成された関数の{}内でしか存在できないため、上のコードの場合、bはmain()から見えず、エラーになります。

#include <stdio.h>

// グローバル変数
int a = 1;

// プロトタイプ宣言
void func1();

void main() {
    printf("%d\n", a); // 実行結果:1
    func1();
}

void func1() {
    //ローカル変数
    int a = 3;
    printf("%d\n", a); // 実行結果:3
}

重複しない方が良いに越したことはありませんが、上のように同じ名前の変数が2つ、グローバル変数とローカル変数でそれぞれで存在する場合、ローカル変数が優先され、変数の衝突がおこりません。

#include <stdio.h>

// グローバル変数
int a = 1;

// プロトタイプ宣言
void func1();

void main() {
    // ローカル変数
    int a = 3;
    printf("%d\n", a);// 実行結果:3
    func1();
}

void func1() {
    printf("%d\n", a); // 実行結果:1
}

上のfunc1()内のaは、呼び出されたのはmain()の中でも、func1()があるのはmain()の外なので、func1()ではaはグローバル変数の方のaです。

#include <stdio.h>

// プロトタイプ宣言
void func1(int );

void main() {
	// ローカル変数
	int a =3;
	
	func1(a);
	printf("%d\n", a);// 実行結果:3
}

void func1(int a) {
	printf("%d\n", ++a); // 実行結果:4
}

引数で渡した値についてですが、ここではコピーが渡されるだけですので、main()の中でfunc1()が呼び出されて実行された後のaの値にはfunc1()での値の変更は反映されません。

参考にさせていただいた外部ページ:
一週間で身につくC言語の基本|第6日目
【C言語】変数名が衝突した時の振る舞いについて

変数のスコープの範囲を例外的に操作する

変数のスコープを操作する際に使われる、「記憶クラス指定子」というものがあります。これを変数や関数に指定すると、配置する記憶領域を通常とは別の場所に確保して、変数や関数の寿命や存在できる範囲(スコープ)を変えられます。

記憶領域

プログラムやデータを記憶する場所の事です。下に種類一覧を書いてみます。

  • プログラム領域(コード領域)
  • データ領域
    • スタック領域(自動記憶領域)
    • ヒープ領域
    • スタティック領域(静的記憶領域)

※上記の外にも、記憶領域として、CPU内のレジスタが使われることもあります。

プログラム領域とは、プログラムを実行するためのプログラムコードが記憶される場所です。

データ領域とは、データの記憶に使われる場所で、メモリーの割り当て方によりさらに細かく、次に説明する3つに分けられます。

スタック領域とは、自動変数(関数の引数や戻り値、一時メモリーなど)が記憶される場所です。領域サイズが小さく、関数を抜けると、確保した領域は消滅します。基本的に変数はここに確保されます。

ヒープ領域とは、動的変数が記憶される場所で、領域は大きく、自分が指定するまで確保されたまま、領域の開始アドレスとサイズによって管理されます(型などが存在しません)。特別に大きい配列などの場合はここに入ります。

スタティック領域とは、外部変数や静的変数が記憶される場所で、プログラム動作中は最後まで確保されます。

大き過ぎてヒープ領域に入りきらない場合、HDDからスワップ領域としてメモリを無理やり確保してくるということもあります(HDDにアクセスので遅くなります)。※スワップ領域についての解説はこちらのページに丸投げさせていただきます。

領域サイズの比較: スワップ領域 > ヒープ領域 > スタック領域 > レジスタ領域
アクセス速度の比較: レジスタ領域 > スタック領域 > ヒープ領域 >> スワップ領域

参考にさせていただいた外部ページ:
memory.dvi
11.記憶クラスとスコープ

記憶クラス指定子

変数や関数に指定し、配置する記憶領域をデフォルトとは別の場所に確保して、変数や関数の寿命や存在できる範囲(スコープ)を変えられます。 以下に種類一覧を載せてみます。

  • 自動変数(ローカル変数): auto
  • 外部変数(グローバル変数): extern
  • 静的変数またはファイル内関数: static
  • レジスタ変数: register
  • 型定義: typedef

autoは、宣言したブロック内のみで有効で、関数が終了すると消滅しますが、記憶クラス指定子を省略した場合デフォルトでこれになるため、実際に使われることはありません。

#include <stdio.h>

void main() {
	auto int a = 7; // auto省略可
	printf("%d", a); // 実行結果:7
}

externは、 グローバル変数が定義される以前にそれを使用したい場合や、大きなプログラムなどでファイル分けしてコードを書く場合などで、他のファイルやモジュールでその変数を使用したい場合にはこのextern指定子を使ってextern宣言をし、外のファイルにその変数があることを示します。ちなみに、関数の外で記憶クラス指定子をつけずに定義したグローバル変数や関数はデフォルトでこれになります。

昨日作ったこのプログラムを転用して、むりやりファイル分けしてみました。

※後日追記:この部分の説明が圧倒的に足りなかったので、後日の記事で補足・解説をしております。わからない方はそちらもお読みいただいて補完をお願いしますm(_ _;)m

  • cube.h
#ifndef _CUBE_H_
#define _CUBE_H_

// プロトタイプ宣言
void cube(int);

#endif // _CUBE_H_
  • cube.c
#include "cube.h"

int answer;

void cube(int l) {
	answer = l * l * l;
}
  • showAnswer.h
#ifndef _SHOW_ANSWER_H_
#define _SHOW_ANSWER_H_

// プロトタイプ宣言
void showAnswer();

#endif // _SHOW_ANSWER_H_
  • showAnswer.c
#include "showAnswer.h"
#include <stdio.h>
extern answer; // 他のファイルで存在しているという宣言

void showAnswer() {
	printf("%d\n----------------------\n", answer);
}
  • main.c
#include <stdio.h>
#include "cube.h"
#include "showAnswer.h"

void main() {
	int a[3] = { 3, 5, 11 };
	for (int i = 0; i < 3; i++) {
		cube(a[i]);
		showAnswer();
	}
}

全てのファイルを用意したら、それをまとめてコンパイルします。コマンドプロンプトにて、ファイルが置かれているのと同じフォルダに移動した後に以下のコードを入力してみてください。

cl main.c cube.c showAnswer.c
複数ファイルをコンパイルする時のコマンドプロンプト画面のスクリーンショット

ここで入力する「cl」の後のファイル名の順番は変えても良いですが、コンパイルされた時に、生成される実行可能ファイル(.exeのファイル)の名前が、一番最初に入力したファイル名で生成されますので、そこだけご留意ください。

複数ファイルを無事にコンパイルした時のコマンドプロンプト画面のスクリーンショット

ちなみに実行結果は下のようになります。

27
----------------------
125
----------------------
1331
----------------------

(今回はヘッダーファイルの中身とか分割の仕方とかパクリに近い形で)大変参考にさせていただいた外部ページ:一週間で身につくC言語の基本|第7日目

staticの場合、場所によって2パターンあります。
関数の{}外で、グローバル変数か関数宣言に指定する場合、その名前は宣言されているファイルやモジュール外に公開されません。
関数の{}内で、ローカル変数に指定する場合、その変数の値は静的に確保され、関数が終わっても消滅しません。

#include <stdio.h>

// プロトタイプ宣言
static void func1(); // 外部ファイルから見えなくなります。

void main() {
	for (int i = 0; i < 4; i++) {
		func1(); // 繰り返すたびに表示される数値が変わります
	}
}

void func1() {
	static float b = 10; // func1関数が終わっても存在し続けます
	b /= 10;
	printf("b = %f\n", b);
}

上のプログラムの実行結果

b = 1.000000
b = 0.100000
b = 0.010000
b = 0.001000

registerを指定すると、記憶領域をCPUのレジスタに割り当てる以外はautoと同じ扱いです。ですが、使用頻度の高い変数に割り当てるとスピードの向上がはかれます。コンパイラにより数に制限があるので、必ずしも割り当てられるわけではありません。

typedefは、これは一応構文上クラス指定子に分類されるのでここで書かせていただきましたが、他とは毛色が違い、これは記憶領域がどうのやら、スコープがどうのというわけではありません。データ型に別名をつける際に使用します。

参考にさせていただいた外部ページ:
memory.dvi
11.記憶クラスとスコープ
記憶クラス
キーワード(C言語) – Wikipedia
一週間で身につくC言語の基本|第6日目
【C言語入門】staticの使い方まとめ(関数、変数、定数、構造体)
C言語の外部変数・局所変数について

あとがき

今日は、記憶領域の事について調べなおしというか、記憶の中の知識が合っているか、いろんなページを巡らせていただいて、照らし合わせしながらの、記事作成でした(^_^;)
結構頑張ったんですが、もし違ったら間違いのご指摘お願いしますm(_ _;)m

ご意見・ご感想・ご質問、また、(記憶領域のこと以外でも)間違ってるよとか、もっといい方法あるよといったご指摘などございましたら、お手数ですがコメント欄やtwitterアカウントほろほろり(@_horo_horori)へお願いしますm(_ _)m

Pocket

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください