C言語の関数で戻り値にポインタを使う

386, 2022-01-10

目次

C言語の関数で戻り値にポインタを使う

C言語の関数はreturn文で戻り値を返せます。
もちろん戻り値でポインタも返すことができます

この記事では関数の戻り値でポインタを返す方法を具体的に解説します
またあわせて注意点も解説します。

関連記事
C言語でポインタと配列を入れ替える(スワップする)方法
C言語の関数ポインタを引数に渡す方法
C言語の配列とポインタについて
C言語の文字列を初期化する方法: 文字配列、文字列ポインタの初期化
C言語の配列の宣言方法: 配列、可変長配列、内部結合な配列、外部結合な配列、ポインタ配列

C言語の関数でポインタを返す

C言語の関数でポインタを返す方法です。
まず定義する関数の戻り値の型をポインタ型にします
ここでは例としてintのポインタ型にします。

int *get_ptr(void) {
    ...
}

関数をこのように定義すると、この関数はポインタを返すことができるようになります。

int *get_ptr(void) {
    ...
    return p;  // pはint型のポインタ
}

この関数が返すポインタを呼び出し側で受け取るには、関数の呼び出し側を↓のように書きます。

int *result = get_ptr();

↑のresultにはget_ptr()が返すポインタが代入されます。

ポインタを返す場合の注意点

関数からポインタを返す時に気をつけたいのが、ローカル変数のポインタです。
関数内で定義したローカル変数のポインタを戻り値で返すのはご法度です。

int *get_ptr(void) {
    int n = 1;
    return &n;  // ダメ、絶対!
}

なぜかというと、関数内のローカル変数というのは、関数が終了すると破棄されます。
破棄されるというのはメモリ上から無くなるということです。
static変数は除きます)

で、そのローカル変数のポインタを戻り値で返した場合、関数の呼び出し側では破棄されたローカル変数のポインタを受け取ることになります。
そのポインタはすでに破棄されている変数のアドレスが入っているので、アクセスするとプログラムがクラッシュする場合があります

これが関数の戻り値にポインタを使う場合の注意点です。

関数の引数のポインタを返す

関数の引数のポインタをそのまま関数の戻り値として返すことも出来ます。

#include <stdio.h>

int *get_ptr(int *ptr) {
    return ptr;  // 引数のポインタをそのまま返す
}

int main(void) {
    int n = 1;
    int *ptr = get_ptr(&n);  // 変数nのポインタを引数に渡す

    printf("%d\n", *ptr);  // 1

    return 0;
}

このような設計は、構造体を使ったモジュールを定義するときに使われることがあります。
たとえばAnimalという構造体と、その構造体に関する関数を定義しているとします。

#include <stdio.h>

struct Animal {
    int age;
};

// 処理に成功したら引数のanimalを返し、
// 処理に失敗したらNULLを返す関数
struct Animal *animal_walk(struct Animal *animal) {
    if (!animal) {
        return NULL;  // 失敗したらNULLポインタを返す
    }

    // ここにwalkの処理

    return animal;  // 成功したらanimalのポインタを返す
}

int main(void) {
    struct Animal animal = {20};

    if (!animal_walk(&animal)) {
        perror("animal_walk");  // 失敗したらエラーを出力
    }

    return 0;
}

上記のanimal_walk()という関数は、処理に成功したら引数のポインタをそのまま返し、処理に失敗したらNULLポインタを返します。
このように関数の成功と失敗を表すのにポインタが使われることもあります
boolなどでもいいですが、構造体のポインタを返すようにしておくことでメソッドチェイン(関数チェイン)などのコーディングが可能になります。

動的確保したメモリのポインタを返す

関数内のローカル変数のポインタを返すのはご法度ですが、動的確保されたメモリのポインタを返すのはよく行われます。

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

// int型のサイズのメモリを確保して返す
int *malloc_int(int n) {
    int *ptr = malloc(sizeof(int));  // intのメモリを確保
    if (!ptr) {
        return NULL;  // メモリ確保に失敗した
    }

    *ptr = n;

    return ptr;  // 確保したメモリのポインタを返す
}

int main(void) {
    // メモリを確保
    int *ptr = malloc_int(123);
    if (!ptr) {
        return 1;
    }

    printf("%d\n", *ptr);  // 123

    free(ptr);  // メモリを解放

    return 0;
}

上記のmalloc_int()関数は、int型のメモリを確保して、そのメモリを引数のnで初期化して、そのポインタを戻り値で返します。
このように動的に確保されたメモリは関数の戻り値で返しても問題になることは少ないです。
ただしメモリの解放漏れに気をつけたほうがいいのは変わりません。

static変数のポインタを返す

関数内のローカル変数でもstatic変数は例外で、そのポインタを返すことができます。

#include <stdio.h>

int *get_static_ptr(void) {
    static int n = 1;  // staticで定義
    return &n;  // ポインタを返す
}

int main(void) {
    // 関数内のstaticなポインタを得る
    int *ptr = get_static_ptr();

    printf("%d\n", *ptr); // 1

    return 0;
}

ただしこのような設計は非常に珍しいです。
私もあまり見たことがないというか、こんなコードは初めて書きました。
理屈ではstaticな変数はプログラムが終了するまで寿命が持ちますので、そのポインタを返しても問題ないということになります。
しかしマルチスレッドなプログラミングになると問題になる可能性があります。
staticな変数はスレッドセーフではないからです。

グローバル変数のポインタを返す

関数ではグローバル変数のポインタも返すことができます。

#include <stdio.h>

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

int *get_global_ptr(void) {
    return &n;  // グローバル変数のポインタを返す
}

int main(void) {
    int *ptr = get_global_ptr();  // グローバル変数のポインタを得る

    printf("%d\n", *ptr);  // 1

    return 0;
}

C言語ではグローバル変数はけっこう使われることがあります。
これは設計によるんですが、マルチスレッドに非対応なプログラムでははなからグローバルな変数を使ってプログラムが書かれることが多くあります。
一般的なプログラミング界隈ではグローバル変数はけっこう嫌われているんですが、C言語ではよく使われます。

グローバル変数もその寿命はプログラムが終了するまでです。
ですのでポインタを返すことが可能です。

文字列定数のポインタを返す

C言語の文字列定数はプログラムのテキストセグメントという領域に保存されます。
そのため文字列定数の寿命はプログラムが終了するまでです。
ですので文字列定数をポインタとして返すこともできます。

#include <stdio.h>

// 文字列定数を返す関数
const char *get_str(void) {
    return "Hello, World!";  // 文字列定数
}

int main(void) {
    const char *s = get_str();  // 文字列定数を得る

    printf("%s\n", s);  // Hello, World!

    return 0;
}

ただし文字列定数は書き換えができません。
無理に書き換えるとプログラムがクラッシュすることがあります。
ですのでポインタにはconstを付けておくのが安全です。

ちなみに文字列定数と文字配列は違いますので注意が必要です
文字配列はローカル変数なので(staticを除いて)関数から返してはいけません。

char str[100];  // 文字配列
"Hello, World!";  // 文字列定数

FIlEのポインタを返す

ファイル入出力の処理でおなじみのFILEポインタは関数内から返すことができます。

#include <stdio.h>

// FILEポインタを返す関数
FILE *open_read_file(const char *fname) {
    return fopen(fname, "r");
}

int main(void) {
    // ファイルを開く
    FILE *fp = open_read_file("./file.c");
    if (!fp) {
        return 1;  // ファイルを開くのに失敗した
    }

    fclose(fp);  // ファイルを閉じる

    return 0;
}

この場合も開いたファイルポインタはfclose()で閉じる必要があります。
閉じておかないとメモリリークなどのバグになったりします。

関連動画

おわりに

今回はC言語の関数の戻り値にポインタを使う方法を解説しました。
ポインタは注意点が多いですが、関数と共に頻繁に使われるC言語の機能です。
ポインタを使って快適なプログラミング・ライフを送りましょう。

(^ _ ^)

ポインタは偉大な発明

(・ v ・)

さんきゅーリッチ

この記事のアンケートを送信する