1. ホーム
  2. c

[解決済み] 関数からC言語の文字列を返す

2022-07-19 13:14:01

質問

ある関数から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 関数からその文字列を返すかです。

どのように解決するのですか?

関数のシグネチャが必要です。

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

背景

これは C & C++ にとって非常に基本的なことですが、もう少し議論しておく必要があります。

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

Windows API (C++) を使用する場合、次のような関数パラメーターを定期的に目にします: "LPCSTR lpszName". sz」の部分は、「string-zero」という概念、つまり、NULL (/zero) ターミネーターを持つバイトの配列を表します。

明確化しました。

この「入門」では、「バイト」と「文字」を使い分けていますが、これはこの方が学習しやすいからです。他の方法 (ワイド文字、マルチバイト文字システム ( mbcs ))という国際的な文字に対応するための方式もあるので、注意が必要です。 UTF-8 はmbcsの一例です。イントロのために、私は静かにこのすべてを「飛び越える」。

メモリです。

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

つまり、この「終端0」がなければ、文字列は存在しません。メモリ上に文字の配列(バッファとも呼ばれます)がぶら下がっている状態です。

データの長寿命化

関数の使い方はこのように。

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

つまり、プログラムがクラッシュしてしまうのは、コンパイラが szBuffer によって使われていたメモリを解放してしまったからです。 printf()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"
}

何が static がここで行うことは(多くのプログラマはこの種の「割り当て」を好まない)、文字列がプログラムのデータセグメントに入れられるということです。つまり、恒久的に割り当てられているのです。

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++ コンパイラーでも大丈夫ですが)。