web-dev-qa-db-ja.com

Cを使って配列を返す

私は比較的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コンパイラは現時点では動作していないので、まだテストしていませんが、解決したいと思います

127
user1506919

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)
}
190
Ed S.

Cの配列の扱いはJavaのものとは非常に異なるので、それに応じて考え方を調整する必要があります。 Cの配列は一流のオブジェクトではありません(つまり、配列式はほとんどの文脈で「配列」を保持しません)。 Cでは、配列式がTまたは単項&演算子のオペランドである場合を除いて、型 "TのN要素配列"の式は暗黙的に "sizeofへのポインタ"型の式に変換されます( "decay")。または、配列式が宣言内の別の配列を初期化するために使用されている文字列リテラルの場合。

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

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の最初のパラメーターはchar *aではなくchar a[6]として宣言されています。 sizeof strでは、配列式はsizeof演算子のオペランドであるため、ポインタ型に変換されないので、配列のバイト数を取得します(6)。

もしあなたが本当に興味を持っているのなら、Dennis Ritchieの The C Languageの開発 を読んで、この扱いがどこから来るのかを理解することができます。 。

その結果、関数は配列型を返すことができず、配列式も代入の対象になることができないため、問題ありません。

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

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);
  ...
}

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

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番目の要素にアクセスします。

あなたはTのN要素配列へのポインタを宣言することができ、同様のことをすることができます:

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つの配列サイズでしか動作しません。第二に、あなたはコードを乱雑にする添え字を適用する前にポインタを間接参照しなければなりません。多次元配列を扱う場合は、配列へのポインタのほうが効果的です。

24
John Bode

この邪悪な実装はどうですか。

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;
}
8
pyrospade

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

    #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;
    }

このテクニックの長所と短所についてコメントをお願いします。私は気にしませんでした。

6
Indinfer

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

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);

}
6
Man of One Way

ここで報告されている他の回答と同様に、ヒープメモリを使用して(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バイトをメモリに占有するので、適切なサイズにしてください。

4
mengo

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

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);
}
2
Michael Dorgan

あなたはこのようなコードを使うことができます:

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.
}

これを行うとき、メモリは後でアドレスをfreeに渡すことによって解放されるべきです。

他の選択肢があります。ルーチンは、既存の構造体の一部である配列(または配列の一部)へのポインターを返すことがあります。呼び出し側が配列を渡す可能性があり、ルーチンは新しい配列にスペースを割り当てるのではなく、単に配列に書き込むだけです。

0