関数からC文字列を返す


109

関数からC文字列を返そうとしていますが、機能しません。これが私のコードです。

char myFunction()
{
    return "My String";
}

main、私はこのようにそれを呼び出しています:

int main()
{
  printf("%s", myFunction());
}

他にもいくつかの方法を試しましたmyFunctionが、機能しません。例えば:

char myFunction()
{
  char array[] = "my string";
  return array;
}

注:ポインターを使用することはできません!

この問題の背景:

何月かを知る機能があります。たとえば、1の場合、1月を返します。

そのため、印刷する場合は、次のようにしますprintf("Month: %s",calculateMonth(month));。ここで問題は、calculateMonth関数からその文字列を返す方法です。


10
残念ながら、この場合ポインタが必要です。
Nick Bedford

1
さて@Hayato私たちはここに大人であり、それが0を返す必要があります知っていると信じて、それは...単なる例LOXを与えるためだった
itsaboutcode

3
return 0デフォルトでは、C99(およびC ++)でのみ暗示され、C90では暗示されません。
hrnt

1
そうすれば、とにかくポインタ操作を実際に分解するだけの馬鹿なハックを除いて、それを行うことができなくなります。ポインターが存在する理由は...:|
GManNickG 2009

回答:


222

関数のシグネチャは次のようにする必要があります。

const char * myFunction()
{
    return "My String";
}

バックグラウンド:

これはCとC ++にとって非常に基本的なことですが、それ以上の議論は必要です。

C(およびC ++)では、文字列は、ゼロバイトで終了する単なるバイトの配列です。したがって、「string-zero」という用語は、この特定の文字列のフレーバーを表すために使用されます。他の種類の文字列がありますが、C(およびC ++)では、このフレーバーは言語自体によって本質的に理解されます。他の言語(Java、Pascalなど)は、「私の文字列」を理解するためにさまざまな方法論を使用しています。

Windows API(C ++に含まれています)を使用したことがある場合、「LPCSTR lpszName」のような非常に定期的に機能するパラメーターが表示されます。「sz」の部分は、「string-zero」のこの概念を表します。ヌル(/ zero)ターミネーターを持つバイトの配列です。

明確化:

この「イントロ」のために、「バイト」と「文字」という言葉を交換可能に使用します。この方法を学ぶ方が簡単だからです。国際文字に対処するために使用される他の方法(ワイド文字、マルチバイト文字システム(mbcs))があることに注意してください。UTF-8はmbcsの例です。イントロのために、私はこれらすべてを静かに「スキップ」します。

メモリ:

つまり、「my string」のような文字列は実際には9 + 1(= 10!)バイトを使用します。これは、最終的に文字列を動的に割り当てるときを知っておくことが重要です。

したがって、この「終了ゼロ」がなければ、文字列はありません。メモリ内にぶら下がっている文字の配列(バッファとも呼ばれます)があります。

データの寿命:

この方法での関数の使用:

const char * myFunction()
{
    return "My String";
}

int main()
{
    const char* szSomeString = myFunction(); // Fraught with problems
    printf("%s", szSomeString);
}

...通常、ランダムな未処理の例外/セグメントの障害などで着陸します。

要するに、私の答えは正しいですが、10回のうち9回は、その方法で使用するとプログラムがクラッシュすることになります。特に、その方法で実行することが「良い習慣」であると考える場合はそうです。要するに、それは一般的にはそうではありません。

たとえば、将来のある時点で、文字列を何らかの方法で操作する必要があるとします。一般的に、コーダーは「簡単な道」をたどり、次のようなコードを書きます(試してみます)。

const char * myFunction(const char* name)
{
    char szBuffer[255];
    snprintf(szBuffer, sizeof(szBuffer), "Hi %s", name);
    return szBuffer;
}

つまり、コンパイラーはszBufferprintf()in main()が呼び出されるまでに使用されていたメモリーを解放している場合とそうでない場合があるため、プログラムがクラッシュします。(コンパイラーもこのような問題を事前に警告する必要があります。)

文字列を返す方法は2つあります。

  1. しばらく存続する(静的または動的に割り当てられた)バッファを返します。C ++では、「ヘルパークラス」を使用します(たとえば、std::string)を使用して、データの寿命(関数の戻り値を変更する必要がある)を処理するか、または
  2. 情報が入力される関数にバッファを渡します。

Cでポインタを使用せずに文字列を使用することは不可能であることに注意してください。私が示したように、それらは同義です。テンプレートクラスのあるC ++でも、バックグラウンドで使用されるバッファー(つまり、ポインター)は常に存在します。

したがって、(現在は変更された質問)により適切に回答するためです。(提供できるさまざまな「その他の答え」があるはずです。)

より安全な回答:

例1、静的に割り当てられた文字列を使用:

const char* calculateMonth(int month)
{
    static char* months[] = {"Jan", "Feb", "Mar" .... };
    static char badFood[] = "Unknown";
    if (month<1 || month>12)
        return badFood; // Choose whatever is appropriate for bad input. Crashing is never appropriate however.
    else
        return months[month-1];
}

int main()
{
    printf("%s", calculateMonth(2)); // Prints "Feb"
}

ここで「静的」とは(多くのプログラマーがこのタイプの「割り当て」を好まない)、文字列がプログラムのデータセグメントに挿入されることです。つまり、永続的に割り当てられます。

C ++に移行する場合は、同様の戦略を使用します。

class Foo
{
    char _someData[12];
public:
    const char* someFunction() const
    { // The final 'const' is to let the compiler know that nothing is changed in the class when this function is called.
        return _someData;
    }
}

...しかしstd::string、自分で使用するためにコードを記述している(他のユーザーと共有するライブラリの一部ではない)場合は、などのヘルパークラスを使用する方がおそらく簡単です。

例2、呼び出し元定義のバッファーの使用:

これは、文字列を渡すためのより簡単な方法です。返されるデータは、発呼者による操作の対象ではありません。つまり、例1は発呼者によって簡単に悪用され、アプリケーションの障害にさらされる可能性があります。このようにすると、コードの行数が多くなりますが、はるかに安全になります。

void calculateMonth(int month, char* pszMonth, int buffersize)
{
    const char* months[] = {"Jan", "Feb", "Mar" .... }; // Allocated dynamically during the function call. (Can be inefficient with a bad compiler)
    if (!pszMonth || buffersize<1)
        return; // Bad input. Let junk deal with junk data.
    if (month<1 || month>12)
    {
        *pszMonth = '\0'; // Return an 'empty' string
        // OR: strncpy(pszMonth, "Bad Month", buffersize-1);
    }
    else
    {
        strncpy(pszMonth, months[month-1], buffersize-1);
    }
    pszMonth[buffersize-1] = '\0'; // Ensure a valid terminating zero! Many people forget this!
}

int main()
{
    char month[16]; // 16 bytes allocated here on the stack.
    calculateMonth(3, month, sizeof(month));
    printf("%s", month); // Prints "Mar"
}

特に他の人が使用するライブラリを作成している場合は、2番目の方法が優れている理由がたくさんあります(特定の割り当て/割り当て解除スキームにロックする必要がないため、サードパーティがコードを壊すことはできません。また、特定のメモリ管理ライブラリにリンクする必要はありません)が、すべてのコードと同様に、何が一番好きかはあなた次第です。そのため、ほとんどの人は、何度も焼き付けられるまで、たとえば1を選択して、それ以上書くことを拒否します;)

免責事項:

私は数年前に引退しました、そして私のCは今少し錆びています。このデモコードはすべてCで正しくコンパイルされます(ただし、どのC ++コンパイラでも問題ありません)。


2
実際にはchar *、Cの文字列リテラルは型であるため、関数はを返す必要がありchar[]ます。ただし、これらを変更してはならないため、戻るconst char*ことをお勧めします(securecoding.cert.org/confluence/x/mwAVを参照)。char *文字列が(残念ながら)char*as引数を期待するレガシーまたは外部ライブラリ関数で使用される場合、文字列が読み取られるだけでなくても、戻り値が必要になる場合があります。一方、C ++にはconst char[]タイプの文字列リテラルがあります(C ++ 11以降、std::stringリテラルを持つこともできます)。
TManhente 2014年

17
@cmroanirgo myプレフィックスは、関数がユーザーによって作成されたことをリーダーに宣言します。私はそのような状況で使用することは完全に合理的だと思います。
2014

4
ここによれば:stackoverflow.com/questions/9970295/…、文字列リテラルを返すことができます
giorgim

6
fraught with problems「データの寿命」セクションでマークされたコードは、実際には完全に有効です。文字列リテラルには、C / C ++で静的な有効期間があります。上記のGiorgiに関するリンクを参照してください。
chengiz、2015年

1
@cmroanirgo文字列リテラルを返すことは良い習慣であり、優れたスタイルです。「問題が多い」わけではなく、10回のうち9回はクラッシュしません。クラッシュすることはありません。80年代のコンパイラ(少なくとも私が使用したもの)でも、文字列リテラルの無制限の寿命を正しくサポートしています。注:回答を編集する意味がわかりません。クラッシュする傾向があると表示されています。
アクセス

12

C文字列は、文字の配列へのポインタとして定義されます。

ポインタを使用できない場合は、定義により文字列を使用できません。


配列を関数に渡して、その配列を操作できますvoid foo( char array[], int length)。もちろん、arrayは内部でのポインタですが、「明示的に」ポインタではないため、配列を学習しているが、ポインタを完全学習していない人にとっては、より直感的です。
jvriesem

12

この新しい関数に注意してください:

const char* myFunction()
{
    static char array[] = "my string";
    return array;
}

「配列」を静的として定義しました。それ以外の場合、関数が終了すると、変数(および返されるポインター)はスコープ外になります。そのメモリ以来スタック上に割り当てられ、それがされます壊れます。この実装の欠点は、コードが再入可能ではなく、スレッドセーフではないことです。

別の代替案は、mallocを使用することです、して文字列をヒープに割り当て、コードの正しい場所で解放することもできます。このコードは再入可能でスレッドセーフです。

コメントに記載されているように、攻撃者はアプリケーションにコードを挿入できるため、これは非常に悪い習慣です(GDBを使用してコードを開き、ブレークポイントを作成して、返された変数の値をオーバーフローおよび楽しみは始まったばかりです)。

呼び出し元にメモリ割り当てを処理させることをお勧めします。この新しい例を見てください:

char* myFunction(char* output_str, size_t max_len)
{
   const char *str = "my string";
   size_t l = strlen(str);
   if (l+1 > max_len) {
      return NULL;
   }
   strcpy(str, str, l);
   return input;
}

変更できるのはユーザーが作成したコンテンツだけであることに注意してください。別の副作用-このコードは、少なくともライブラリの観点からはスレッドセーフです。このメソッドを呼び出すプログラマは、使用されるメモリセクションがスレッドセーフであることを確認する必要があります。


2
これは一般的に物事を進めるには悪い方法です。char *は、周囲のコードによって操作できます。つまり、次のようなことができます。strcpy(myFunction()、 "本当に長い文字列"); そして、プログラムはアクセス違反のためにクラッシュします。
cmroanirgo 2014年

「ユーザーが使うもの」の近くに何か不足しています
Peter Mortensen

8

あなたの問題は、関数の戻り値の型にあります-それは以下でなければなりません:

char *myFunction()

...そして、元の処方が機能します。

できないことに注意してください行のどこかにポインタが含まれていとC文字列。

また、コンパイラの警告を上げてください。明示的なキャストなしでa char *をに変換する戻り行について警告する必要がありますchar


1
文字列はリテラルなので、署名はconst char *である必要があると思いますが、私が間違っていない場合、コンパイラはこれを受け入れます。
ルカ

5

新しく追加された質問のバックストーリーに基づいて、月の1から12までの整数を返し、main()関数にswitchステートメントまたはif-elseラダーを使用して何を印刷するかを決定しませんか?確かに、これは最善の方法ではありません。char*はそうですが、このようなクラスのコンテキストでは、おそらく最もエレガントだと思います。


3

メイン関数である呼び出し元で配列を作成し、myFunction()である呼び出し先に配列を渡すことができます。したがって、myFunctionは文字列を配列に入力できます。ただし、myFunction()を次のように宣言する必要があります

char* myFunction(char * buf, int buf_len){
  strncpy(buf, "my string", buf_len);
  return buf;
}

メイン関数では、myFunctionを次のように呼び出す必要があります。

char array[51];
memset(array, 0, 51); /* All bytes are set to '\0' */
printf("%s", myFunction(array, 50)); /* The buf_len argument  is 50, not 51. This is to make sure the string in buf is always null-terminated (array[50] is always '\0') */

ただし、ポインターは引き続き使用されます。


2

関数の戻り値の型は1文字(char)です。文字配列の最初の要素へのポインタを返す必要があります。ポインタを使用できない場合は、ねじ込まれています。:(


2

またはこれはどうですか:

void print_month(int month)
{
    switch (month)
    {
        case 0:
            printf("January");
            break;
        case 1:
            printf("february");
            break;
        ...etc...
    }
}

そして、別の場所で計算する月でそれを呼び出します。


1
+1はOPが尋ねたものではありませんが、ポインターを使用できないため、これはおそらく割り当てがあなたに期待することです。
Vitim.us 2013

printfもポインタを使用します。ポインターはナイフのようなものです-生活と仕事に欠かせませんが、ハンドルでそれを持ち、鋭い側を使って一緒に切る必要があります。残念なことに、関数定義にスペースを配置することは、多くの新しいCプログラマにとって頭の痛いバグです。char * func(char * s); char func(char * s); char func * char * s); すべて同じですが、外観はすべて異なります。また、混乱を招くため、*はポインターである変数の逆参照演算子でもあります。
クリスリード

1

A charは1バイト文字です。文字列を格納することも、ポインタ(あなたがどうやらそうすることもできない)を格納することもできません。したがって、ポインタ(char[]構文糖衣)を使用せずに問題を解決することはできません。


1

本当にポインタを使用できない場合は、次のようにします。

char get_string_char(int index)
{
    static char array[] = "my string";
    return array[index];
}

int main()
{
    for (int i = 0; i < 9; ++i)
        printf("%c", get_string_char(i));
    printf("\n");
    return 0;
}

マジックナンバー9はひどいものであり、これは優れたプログラミングの例ではありません。しかし、あなたは要点を理解します。ポインタと配列は同じもの(種類)であるため、これは少しごまかしです。


通常、宿題の問題にそのようなソリューションを実装する必要がある場合、予備的な仮定は間違っています。
hrnt

1

さて、あなたのコードStringでは(Cではnullで終わる文字の配列にすぎません)を返そうとしていますが、関数の戻り値の型がchar問題を引き起こしています。代わりに、次のように書く必要があります。

const char* myFunction()
{

    return "My String";

}

またconst、Cのリテラルは変更できないため、Cのリテラルをポインターに割り当てる間、型を修飾することは常に良いことです。


0

関数プロトタイプは、関数がcharを返すと述べています。したがって、関数で文字列を返すことはできません。



0

関数から文字列を返す

#include <stdio.h>

const char* greet() {
  return "Hello";
}

int main(void) {
  printf("%s", greet());
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.