Cを使用して配列を返す


152

私はCに比較的慣れていないので、配列を処理するメソッドについてサポートが必要です。Javaプログラミングから来て、私はint [] method()配列を返すために言うことができることに慣れています。ただし、Cでは、配列を返すときに配列のポインタを使用する必要があることがわかりました。私は新しいプログラマーであり、私がこれまで調べてきた多くのフォーラムがあっても、これをまったく理解していません。

基本的に、Cでchar配列を返すメソッドを記述しようとしています。メソッドにreturnArrayという名前の配列を提供します。前の配列から新しい配列を作成し、その配列へのポインターを返します。これを開始する方法と、配列から送信されたポインターを読み取る方法について、いくつかの助けが必要です。これを説明する助けがありがたいです。

配列を返す関数のコード形式の提案

char *returnArray(char array []){
 char returned [10];
 //methods to pull values from array, interpret them, and then create new array
 return &(returned[0]); //is this correct?
} 

関数の呼び出し元

int main(){
 int i=0;
 char array []={1,0,0,0,0,1,1};
 char arrayCount=0;
 char* returnedArray = returnArray(&arrayCount); ///is this correct?
 for (i=0; i<10;i++)
  printf(%d, ",", returnedArray[i]);  //is this correctly formatted?
}

私のCコンパイラは現在動作していないため、これはまだテストしていませんが、これを理解したいと思います


コードサンプルに示されているように、戻り配列は既知のサイズですか?回答で述べたスタックの問題以外に私が目にする唯一の落とし穴は、Cでのポインター/配列の動作方法を考えると、戻り配列が不確定なサイズである場合、その大きさがわからないということです。
strangefreeworld 2012

はい、着信アレイのサイズを常に知っています。入力配列と出力配列のサイズは変更されません。
user1506919 2012

1
C言語の開発* -bell-labs.com
usr

回答:


225

Cの関数から配列を返すことはできません。これを行うこともできません(すべきではありません)。

char *returnArray(char array []){
 char returned [10];
 //methods to pull values from array, interpret them, and then create new array
 return &(returned[0]); //is this correct?
} 

returned 自動ストレージ期間で作成され、宣言スコープを離れると、つまり関数が戻ると、その参照は無効になります。

関数内のメモリを動的に割り当てるか、呼び出し元から提供された事前に割り当てられたバッファを埋める必要があります。

オプション1:

関数内のメモリを動的に割り当てます(割り当て解除を担当する呼び出し元ret

char *foo(int count) {
    char *ret = malloc(count);
    if(!ret)
        return NULL;

    for(int i = 0; i < count; ++i) 
        ret[i] = i;

    return ret;
}

次のように呼び出します。

int main() {
    char *p = foo(10);
    if(p) {
        // do stuff with p
        free(p);
    }

    return 0;
}

オプション2:

呼び出し元によって提供された事前に割り当てられたバッファを埋めます(呼び出し元が割り当てbufて関数に渡します)

void foo(char *buf, int count) {
    for(int i = 0; i < count; ++i)
        buf[i] = i;
}

そして、次のように呼び出します。

int main() {
    char arr[10] = {0};
    foo(arr, 10);
    // No need to deallocate because we allocated 
    // arr with automatic storage duration.
    // If we had dynamically allocated it
    // (i.e. malloc or some variant) then we 
    // would need to call free(arr)
}

33
オプション3:(静的配列)
moooeeeep

5
@moooeeeep:ええ、物事を単純にするために意図的に省略しましたが、はい、関数内から宣言された静的データへのポインターを返すことができます。
Ed S.

3
@ user1506919:誰がメモリの割り当てと割り当て解除を行うかは明らかなので、実際にはオプション2を選択しますが、例を追加します。
エドS.

7
オプション4:固定サイズの配列を含む構造体を返します。
トッドリーマン、2015年

2
オプション5:固定サイズの配列を含む共用体を返します。
sqr163

27

Cの配列の扱いはJava の扱いとは非常に異なり、それに応じて考え方を調整する必要があります。Cの配列は、ファーストクラスのオブジェクトではありません(つまり、配列式は、ほとんどのコンテキストでその「配列性」を保持しません)。Cでは、「N要素配列のT型」のT式は、配列式がsizeof単項演算&子または単項演算子のオペランドである場合、または配列式は、宣言内の別の配列を初期化するために使用される文字列リテラルです。

特に、これは配列式を関数に渡して配列型として受け取ることができないことを意味します。関数は実際にはポインタ型を受け取ります。

void foo(char *a, size_t asize)
{
  // do something with a
}

int bar(void)
{
  char str[6] = "Hello";
  foo(str, sizeof str);
}

への呼び出しでfooは、式strは型char [6]からchar *に変換されます。そのため、の最初のパラメータがの代わりにfoo宣言されます。では、配列式は演算子のオペランドなので、ポインター型に変換されないため、配列内のバイト数を取得します(6)。 char *achar a[6]sizeof strsizeof

あなたがいる場合は、本当に興味を持って、あなたはデニス・リッチーさん読み取ることができるC言語のザ・開発を、この治療はどこから来るのかを理解すること。

結局のところ、関数は配列型を返すことができません。配列式も割り当てのターゲットにできないため、これは問題ありません。

最も安全な方法は、呼び出し元が配列を定義し、そのアドレスとサイズを、書き込むことになっている関数に渡すことです。

void returnArray(const char *srcArray, size_t srcSize, char *dstArray, char dstSize)
{
  ...
  dstArray[i] = some_value_derived_from(srcArray[i]);
  ...
}

int main(void)
{
  char src[] = "This is a test";
  char dst[sizeof src];
  ...
  returnArray(src, sizeof src, dst, sizeof dst);
  ...
}

別の方法は、関数が配列を動的に割り当て、ポインターとサイズを返すことです。

char *returnArray(const char *srcArray, size_t srcSize, size_t *dstSize)
{
  char *dstArray = malloc(srcSize);
  if (dstArray)
  {
    *dstSize = srcSize;
    ...
  }
  return dstArray;
}

int main(void)
{
  char src[] = "This is a test";
  char *dst;
  size_t dstSize;

  dst = returnArray(src, sizeof src, &dstSize);
  ...
  free(dst);
  ...
}

この場合、呼び出し元は、freeライブラリ関数を使用して配列の割り当てを解除する必要があります。

dst上記のコードでは、への単純なポインタcharであり、の配列へのポインタではないことに注意してくださいchar。Cのポインターと配列のセマンティクスは[]、配列型またはポインター型の式に添字演算子を適用できるようなものです。両方src[i]dst[i]アクセスするiアレイの」番目の要素を(たとえのみsrc配列型を有します)。

あなたはできるのN要素の配列へのポインタを宣言Tし、似たような操作を行います。

char (*returnArray(const char *srcArr, size_t srcSize))[SOME_SIZE]
{
  char (*dstArr)[SOME_SIZE] = malloc(sizeof *dstArr);
  if (dstArr)
  {
    ...
    (*dstArr)[i] = ...;
    ...
  }
  return dstArr;
}

int main(void)
{
  char src[] = "This is a test";
  char (*dst)[SOME_SIZE];
  ...
  dst = returnArray(src, sizeof src);
  ...
  printf("%c", (*dst)[j]);
  ...
}

上記のいくつかの欠点。まず第一に、古いバージョンのC SOME_SIZEはコンパイル時の定数であると想定しています。つまり、関数は1つの配列サイズでしか機能しません。次に、添え字を適用する前にポインタを逆参照する必要があります。これにより、コードが乱雑になります。配列へのポインターは、多次元配列を処理する場合により適切に機能します。


2
「Cの開発」へのリンクが壊れています... bell-labs.com/usr/dmr/www/chist.html
Dr.Queso

@Kundor:bar受け取るのは、配列ではなくポインターです。関数パラメータ宣言のコンテキストでは、T a[N]とのT a[]両方として扱われT *aます。
John Bode

@JohnBode:その通りです!どういうわけか、固定サイズの配列がスタックに渡されると思いました。何年も前に、配列のサイズをパラメーターのシグニチャーで指定する必要があることに気付いたときのことを思い出しましたが、混乱していたに違いありません。
Nick Matteo

@JohnBode、2番目のコード部分の最初の行:void returnArray(const char *srcArray, size_t srcSize, char *dstArray, char dstSize)最後のパラメーターはsize_tnot型である必要がありcharます。
Seyfi、

11

これが、与えられた問題に対する最良の解決策または好ましい解決策であると言っているのではありません。ただし、関数が構造体を返す可能性があることを覚えておくと便利です。関数は配列を返すことはできませんが、配列は構造体でラップすることができ、関数は構造体を返すことができるため、配列を運ぶことができます。これは固定長配列で機能します。

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

    typedef
    struct 
    {
        char v[10];
    } CHAR_ARRAY;



    CHAR_ARRAY returnArray(CHAR_ARRAY array_in, int size)
    {
        CHAR_ARRAY returned;

        /*
        . . . methods to pull values from array, interpret them, and then create new array
        */

        for (int i = 0;  i < size; i++ )
            returned.v[i] = array_in.v[i] + 1;

        return returned; // Works!
    } 




    int main(int argc, char * argv[])
    {
        CHAR_ARRAY array = {1,0,0,0,0,1,1};

        char arrayCount = 7;

        CHAR_ARRAY returnedArray = returnArray(array, arrayCount); 

        for (int i = 0; i < arrayCount; i++)
            printf("%d, ", returnedArray.v[i]);  //is this correctly formatted?

        getchar();
        return 0;
    }

この手法の長所と短所についてコメントを募集します。私はそうする気になりませんでした。


1
なぜこれが受け入れられない答えなのかは不明です。問題は、配列へのポインタを返すことが可能かどうかではありませんでした。
フランクパック

CHAR_ARRAY returnedヒープにメモリが割り当てられていますか?それは確かにスタック上に置くことはできません(returnArray()右のスタックフレーム内?
Minh Tran

9

このおいしい邪悪な実装はどうですか?

array.h

#define IMPORT_ARRAY(TYPE)    \
    \
struct TYPE##Array {    \
    TYPE* contents;    \
    size_t size;    \
};    \
    \
struct TYPE##Array new_##TYPE##Array() {    \
    struct TYPE##Array a;    \
    a.contents = NULL;    \
    a.size = 0;    \
    return a;    \
}    \
    \
void array_add(struct TYPE##Array* o, TYPE value) {    \
    TYPE* a = malloc((o->size + 1) * sizeof(TYPE));    \
    TYPE i;    \
    for(i = 0; i < o->size; ++i) {    \
        a[i] = o->contents[i];    \
    }    \
    ++(o->size);    \
    a[o->size - 1] = value;    \
    free(o->contents);    \
    o->contents = a;    \
}    \
void array_destroy(struct TYPE##Array* o) {    \
    free(o->contents);    \
}    \
TYPE* array_begin(struct TYPE##Array* o) {    \
    return o->contents;    \
}    \
TYPE* array_end(struct TYPE##Array* o) {    \
    return o->contents + o->size;    \
}

main.c

#include <stdlib.h>
#include "array.h"

IMPORT_ARRAY(int);

struct intArray return_an_array() {
    struct intArray a;
    a = new_intArray();
    array_add(&a, 1);
    array_add(&a, 2);
    array_add(&a, 3);
    return a;
}

int main() {
    struct intArray a;
    int* it;
    int* begin;
    int* end;
    a = return_an_array();
    begin = array_begin(&a);
    end = array_end(&a);
    for(it = begin; it != end; ++it) {
        printf("%d ", *it);
    }
    array_destroy(&a);
    getchar();
    return 0;
}

2
これは私の好奇心をかき立てるほど悪魔的に美味しいです。あなたがそこで何をしたかについてもう少し説明できますか、あるいはあなたが呼ぶこの美味しさについての読書を提案できますか?前もって感謝します。
Unheilig 2014年

1
@Unheilig-これにはかなりの潜在的なバグがあることに注意してください。これは概念の証明にすぎません。とはいえ、トリックはをstructコンテナ/オブジェクトとして返すことです。C ++ std :: vectorのように考えてください。プリプロセッサはint、このバージョンをに拡張しstruct intArray { int* contents; int size; };ます。
ピロスペード、2014年

1
私はアプローチが好きです。pro:これは一般的なソリューションです。反対:メモリを大量に消費するソリューション。kownサイズのベクトルには最適ではありません。とにかく、これは初期サイズの割り当てでアップグレードできます。私はdefinitleyにいくつかの割り当てチェックを追加します。開始するのに非常に良い提案:)
urkon

オブジェクト指向のeskは、マッシュマッシュを備えています。私はそれが好きです。
ジャックギフィン2018年

6

あなたのケースでは、スタック上に配列を作成していて、関数スコープを離れると、配列は割り当て解除されます。代わりに、動的に割り当てられた配列を作成し、その配列へのポインターを返します。

char * returnArray(char *arr, int size) {
    char *new_arr = malloc(sizeof(char) * size);
    for(int i = 0; i < size; ++i) {
        new_arr[i] = arr[i];
    }
    return new_arr;
}

int main() {

    char arr[7]= {1,0,0,0,0,1,1};
    char *new_arr = returnArray(arr, 7);

    // don't forget to free the memory after you're done with the array
    free(new_arr);

}

2
newCには演算子はありません。C++です。
エリックポストピシル

1
そして、でsizeof(char)あることが保証されている1ので、この場合は、そのビットをから削除できますmalloc
Ed S.

わかりましたので、新しい配列の内容を出力したい場合、「printf」ステートメントを実行するだけで「returnedArray」を「arr」に置き換えることができますか?
user1506919 2012

関数を適切に呼び出していません(シグニチャーで2つの引数が必要な場合は1つの引数のみ)。
Ed S.

あなたは通過してい&arrます。あなたはしたいarrことchar *、そしてそれが使用して渡しますarr
クリス

4

ここで報告される他の回答と同様に、ヒープメモリを使用して(malloc()呼び出しを介して実行できますが、常にメモリを管理する必要があります関数を呼び出すたびにfree()関数を使用します)。静的配列でそれを行うこともできます:

char* returnArrayPointer() 
{
static char array[SIZE];

// do something in your array here

return array; 
}

メモリ管理を気にすることなく使用できます。

int main() 
{
char* myArray = returnArrayPointer();
/* use your array here */
/* don't worry to free memory here */
}

この例では、配列の定義でstaticキーワードを使用して、配列の有効期間をapplication-longに設定する必要があります。これにより、returnステートメントの後でそれが破棄されることはありません。もちろん、このようにして、アプリケーションの寿命全体でメモリのSIZEバイトを使用するので、適切にサイズ設定してください!


2

メソッドは、ひどく失敗するローカルスタック変数を返します。配列を返すには、関数の外で配列を作成し、アドレスで関数に渡し、それを変更するか、ヒープに配列を作成してその変数を返します。どちらも機能しますが、1つ目は正しく機能させるために動的メモリ割り当てを必要としません。

void returnArray(int size, char *retArray)
{
  // work directly with retArray or memcpy into it from elsewhere like
  // memcpy(retArray, localArray, size); 
}

#define ARRAY_SIZE 20

int main(void)
{
  char foo[ARRAY_SIZE];
  returnArray(ARRAY_SIZE, foo);
}

0

次のようなコードを使用できます。

char *MyFunction(some arguments...)
{
    char *pointer = malloc(size for the new array);
    if (!pointer)
        An error occurred, abort or do something about the error.
    return pointer; // Return address of memory to the caller.
}

これを行うと、後でアドレスを解放することにより、メモリを解放する必要があります。

他のオプションがあります。ルーチンは、既存の構造の一部である配列(または配列の一部)へのポインターを返す場合があります。呼び出し元は配列を渡す可能性があり、ルーチンは新しい配列にスペースを割り当てるのではなく、単に配列に書き込みます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.