【C言語/C++】 配列は戻り値にできない【配列を適切に返す方法】


C言語では関数の戻り値として配列を返すことができません。

// Function cannot return array type 'int [3]'
// Brackets are not allowed here; to declare an array, place the brackets after the identifier
int[3] getDate() { return {2016, 2, 29}; }

// Function cannot return array type 'int [3]'
int getDate()[3] { return {2016, 2, 29}; }

代替案

配列を返すための6つの代替案を紹介します。

固定長配列を返す場合は「# 配列のポインタ渡し」の利用が適切です。可変長配列や定数型の配列を返す場合は# ポインタとして返す方法が利用できます。

スポンサーリンク

配列のポインタ渡し

もっともメジャーな方法です。既定の配列を引数として受け取り、値を代入します。

void getDateArray(int date[3]) {
  date[0] = 2016;
  date[1] = 2;
  date[2] = 29;
}

呼び出し側では、事前に固定長配列を宣言しておく必要があります。

int main() {
  int a[3];
  getDateArray(a);
  printf("%d年%d月%d日", a[0], a[1], a[2]); // 2016年2月29日
}
配列はポインタと等価関係にあるため、呼び出し元の配列aはきちんと書き換わります。

ポインタとして返す

メモリの動的確保を行う方法

配列の代わりに動的確保されたポインタを返す方法もあります。

int *getDatePointer() {
  int *date = malloc(sizeof(int) * 3);
  date[0] = 2016;
  date[1] = 2;
  date[2] = 29;
  return date;
}

受け取ったポインタは最後に開放する必要があります。

int main() {
  int *date = getDatePointer();
  printf("%d年%d月%d日", date[0], date[1], date[2]);
  free(date); // 開放
}
こちらの方法は利用用途が限られます。関数の結果が可変長配列/動的配列になるような場合に使うと良いでしょう。

配列が定数値の場合

定数値として宣言された配列であれば、直接ポインタとして返すことができます。

// 定数
static const int referenceDate[] = {2001, 1, 1};
const int *getReferenceDate() {
  return referenceDate;
}

静的変数を返すこともできます。

const int *getReferenceDate() {
  // 静的変数
  static const int referenceDate[] = {2001, 1, 1};
  return referenceDate;
}

静的オブジェクトであれば、用途によっては定数宣言されていない配列を返すこともできます。

char *ctoa(char c) {
  static char str[2] = {'\0', '\0'};
  str[0] = c;
  return str;
}
この手のコードはマルチスレッドに対応できないため、利用用途が限られます。

注意点

ローカル変数を戻り値として利用しないようにしてください。

int *getDate() {
  int date[3] = {2016, 2, 29};
  
  // 警告: Address of stack memory associated with local variable 'date' returned
  return date;
  
  // Clang(C言語コンパイラ)の場合は警告されないが危険
  // Clang++(C++コンパイラ)の場合は警告される
  return (int[]){2016, 2, 29};
  // 警告(Clang++): Returning address of local temporary object
  // 警告(Clang++): Pointer is initialized by a temporary array, which will be destroyed at the end of the full-expression
}

date配列はgetDate関数内部でのみ有効です。受け取り側では無効な値になるため注意してください。

また複合リテラル(int[]){2016, 2, 29}は自動変数dateと同等の記憶領域・記憶期間を持ちますので、date配列と同等の問題を引き起こします。上記のコードはコンパイラによっては警告がされないため注意が必要です。

構造体を利用する

ポインタを用いない方法としては、構造体の利用が考えられます。

typedef struct { int year, month, day; } Date;
Date getDateSet() { return (Date){2016, 2, 29}; }

int main() {
  Date date = getDateSet();
  printf("%d年%d月%d日", date.year, date.month, date.day);
}

わざわざ配列形式にする必要のないようなデータに関しては、構造体でやり取りしたほうが色々と便利です。

配列型メンバ変数を活用する

配列そのものを構造体でラップする方法もあります。

typedef struct { int array[3]; } DateArray;
DateArray getDateArray() { return (DateArray){{2016, 2, 29}}; }

int main() {
  DateArray date = getDateArray();
  printf("%d年%d月%d日", date.array[0], date.array[1], date.array[2]);
  
  int *a = date.array;
  printf("%d年%d月%d日", a[0], a[1], a[2]);
}

std::arrayを利用する(C++)

C++の場合は、固定長配列クラスstd::arrayを利用することが一般的です。

#include <array> // std::array

std::array<int, 3> getDate() {
  return {2017, 6, 28};
}

auto main() -> int {
  std::array<int, 3> date = getDate();
  printf("%d年%d月%d日", date[0], date[1], date[2]);
}

可変長配列の場合は、std::vectorを利用します。

std::vector<int> date = {2017, 6};
date.push_back(28);
printf("%d年%d月%d日", date[0], date[1], date[2]);

配列の参照を返す

豆知識として、配列の参照を返す方法も紹介しておきます。

おそらくあまり使われない方法かと思われます。
const int (*getDateRef())[3] {
  static const int date[] = {2017, 1, 8};
  return &date;
}
int *p = *getDateRef(); // (int *){2017, 1, 8}
printf("%d年%d月", **getDateRef(), p[1]); // 2017年1月
printf("%ld", sizeof(*getDateRef()));    // 12

このように、戻り値に含まれる配列のサイズ12を取得することができるという特徴があります。

ちなみにこの場合のgetDateRefは「配列を指したポインタを返す関数」です。戻り値の型は「配列へのポインタ(int (*pa)[3])」です。これは「ポインタの配列(int *ap[3])」とは真逆の性質を持ったものであり、感覚的には「ポインタのポインタ(int **dp)」(ダブルポインタ)に近いものだと思ってもらうとよいでしょう。

広告

関連するオススメの記事