1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【C言語】アドレスとポインタ、動的確保

Last updated at Posted at 2024-11-12

アドレス

備考 : 変数や関数等がメモリに保存されている場所

example.c
#include <stdio.h>

int main()
{
    int tmp = 0;
    
    // &(アンパサンド)を宣言後の変数の前につけると、
    // アドレスが確認できる
    printf("変数 tmp のアドレス : %p\n", &tmp);
    
    return 0;
}

ポインタ

用途 : アドレス(変数や関数等がメモリに保存されている場所)を格納する

  • 宣言
 * 変数名;
  • 定義
変数名 = アドレス(&変数);
  • アドレスに格納されている値
*変数名

配列とポインタ

example.c
#include <stdio.h>

int main()
{
    int iArray[5] = { 10, 8, 6, 4, 2, };
    
    // 配列の添え字番号を指定しない場合、
    // 変数は先頭のアドレス(今回はiArray[0])になる
    // ex)iArray == &iArray[0]
    int* p = iArray;
    
    // 配列の先頭から順に要素のアドレスと値を出力
    for (int i = 0; i < 5; i++, p++)
    {
        printf("アドレス : %p\n", p);
        printf("値 : %d\n", *p);
    }

    // 配列の先頭から、数字 * 型のサイズ分(iArray[0 + 数字])アドレスの値を出力
    printf("配列[0 + 数字] の値 : %d\n", *(iArray + 数字));

    return 0;
}

構造体とポインタ

example.c
#include <stdio.h>

int main()
{
    struct Test
    {
        int a;
        int b;
        char c;
        float d;
        bool e;
    };
    struct Test t;
    
    Test* pTest = &t;
    pTest->a = 0;
    pTest->b = 10;
    pTest->c = '0';
    pTest->d = 0.0f;
    pTest->e = true;

    return 0;
}

※構造体のポインタは、.(ドット)ではなく、->(アロー)を用いる

アドレス渡し

関数は通常、渡された引数の値をコピーして関数内に渡します(値渡し)。
ただし、関数内で引数の値を変更したい場合は、アドレス渡しを用います。

example.c
#include <stdio.h>

/// <summary>
/// アドレスに格納されている値を1増やす
/// </summary>
/// <param name="_tmp">tmpのポインタ</param>
void Add(int* _tmp)
{
	*_tmp += 1;
}

int main()
{
	int tmp = 10;
	// アドレス渡し
	Add(&tmp);
	printf("tmp = %d\n", tmp);
	return 0;
}

参照(C++のみ)

型& 変数名

int a = 10;
int &b = a; // bがaと同じメモリ領域にアクセス

b++; // aが11になる

抽象的に言うと、bとaは一心同体

参照渡し(C++、C#のみ)

  • 備考
    引数に型& 変数名を定義して、変数のメモリ上のアドレスを渡すことで、
    関数内でその変数の値を直接変更できるようにする手法。
    アドレス渡しよりも手順が簡潔、かつ、アドレスの誤操作がなくなるため、
    安全性が高くなる。
  • C++
 関数名(& 変数名)...
  • C#
 関数名(ref  変数名)...

メモリ領域(動的確保の前に...)

  1. グローバル領域・スタティック領域
    グローバル変数やstatic(静的)な変数が保存される
  2. スタック領域
    自動変数(関数内で宣言される変数等)が保存される
  3. ヒープ領域
    動的確保によって確保される

※ヒープ領域はプログラマー自身が明示的に解放しない限り確保され続ける
 解放を忘れると解放せずに終了(メモリーリーク)を起こす
 また、解放したアドレスをプログラム上で触るとエラーが起きる

動的確保(C/C++のみ)

用途 : プログラマー自身で実行中のメモリを柔軟に管理する
ex)スコープからの独立、複数の関数でメモリ領域の共有、メモリ領域を必要分のみ確保etc...

mallocとfree

example_malloc_free.c
#include <stdio.h>
#include <stdlib.h>

int main()
{
	// 動的確保
    // malloc : 確保されたメモリのアドレスをvoidポインタとして返す
	int* iPtr = (int*)malloc(sizeof(int));
	char* str = (char*)malloc(sizeof(char) * 32);

	if (iPtr == NULL)
	{
		printf("memory error");
		return -1;
	}
	*iPtr = 10;
	printf("%d\n", *iPtr);

	if (str == NULL)
	{
		printf("memory error");
		return -1;
	}
	strcpy_s(str, 32, "Hello World");
	printf("%s\n", str);

	// メモリ領域の解放
	free(str);
	free(iPtr);

	return 0;
}

newとdelete(C++、C#)

example_new_delete.cpp
#include <iostream>

int main()
{
	// 動的確保
	// new : 確保したオブジェクトのポインタを返す
	int* iPtr = new int;
	char* str = new char[32];

	if (iPtr == nullptr)
	{
		printf("memory error");
		return -1;
	}
	*iPtr = 10;
    std::cout << *iPtr << std::endl;

	strcpy_s(str, 32, "Hello World");
    std::cout << str << std::endl;

	// メモリ領域の解放
	delete[] str; // 配列なので delete[]
	delete iPtr;  // 単一の要素なので delete

	return 0;
}

※C#の場合、ガベージコレクションがあるので、deleteは不要

スマートポインタ(C++11以上のみ)

ポインタからの変更点 : メモリ管理の自動化(メモリリークの防止)

スマートポインタには主に以下の三種類を指す

  • std::unique_ptr(ユニークポインタ)
  • std::shared_ptr(シェアードポインタ)
  • std::weak_ptr(ウィークポインタ)

インクルード(#include)

#include <memory>

std::unique_ptr / std::shared_ptr共通

  • 宣言
std::〇〇_ptr<> 変数名;
  • 定義
変数名.reset(new ());
変数名.reset(new [要素数]); // 配列対応(C++17以降)
変数名 = std::make_〇〇<>(); // std::make_uniqueはC++14、std::make_sharedはC++11から
  • 宣言・定義
std::〇〇_ptr<> 変数名(new ());

// 配列
std::unique_ptr<[]> 変数名(new [要素数]);
std::shared_ptr<> 変数名(new int[要素数], std::default_delete<int[]>());
std::unique_ptr<[]> 変数名 = std::make_unique<int[]>(要素数); // 明示的な書き方(C++14以降)
std::shared_ptr<[]> 変数名(new int[要素数]); // 配列対応(C++17以降)
std::shared_ptr<[]> = std::make_shared<int[]>(10); // 明示的な書き方(C++20以降)

// テンプレート型の推論補助(C++17以降)
std::〇〇_ptr 変数名 = std::make_〇〇<>();
  • アドレスの取得
変数名.get();
  • 明示的に解放(書かなくても良い)
変数名.reset();

std::unique_ptr

備考 : あるメモリに対する所有権を持つポインタが、ただ一つであることを保証するスマートポインタ

// 所有権の譲渡
// ※uPtr〇はstd::unique_prt型とする
uPtr2 = std::move(uPtr1);

std::shared_ptr

備考 : 同一のメモリの所有権を複数で共有できるようにしたスマートポインタ

// 共有数の取得
// ※sPtrはstd::shared_prt型とする
sPtr.use_count();

循環参照

std::shared_ptrのメモリ解放を行う場合、共有数が0ではない場合、解放できない
それに伴い、所有権を相互的に参照(循環参照)した場合、メモリリークが発生する。

std::weak_ptr

用途 : 循環参照を防ぐ
※循環参照を防ぐ安全性よりパフォーマンスを求める場合、生ポインタを推奨
 (オーバーヘッド等の理由により、生ポインタよりはパフォーマンスが下がるため)

example.cpp
#include <iostream>
#include <memory>

int main()
{
   // 弱参照
   std::shared_ptr<int> sPtr = std::make_shared<int>(10);
   std::weak_ptr<int> wPtr(sPtr);

   // 参照先のメモリが解放されていないなら
   if (!wPtr.expired())
   {
       // 所有権を共有する
       std::shared_ptr<int> sPtrTmp = wPtr.lock();
   }

   // 明示的な参照の終了(書かなくても良い)
   wPtr.reset();
   
   return 0;
}

スマートポインタの参考記事

C++20スマートポインタ入門

コメント

ポインタの仕様は複雑怪奇ですので、
何かご意見あれば、どしどしコメントお願いします!

1
0
6

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?