[c] 함수에서 C 문자열 반환

함수에서 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 ++)에서 문자열은 0 바이트로 끝나는 바이트 배열 일뿐입니다. 따라서 “문자열 0″이라는 용어는이 특정 문자열의 특징을 나타내는 데 사용됩니다. 다른 종류의 문자열이 있지만 C (& C ++)에서이 풍미는 본질적으로 언어 자체에 의해 이해됩니다. 다른 언어 (Java, Pascal 등)는 “my string”을 이해하기 위해 다른 방법론을 사용합니다.

Windows API (C ++에 있음)를 사용하는 경우 “LPCSTR lpszName”과 같은 매우 정기적으로 함수 매개 변수가 표시됩니다. ‘sz’부분은 ‘문자열 0’이라는 개념을 나타냅니다. 즉, 널 (/ 0) 종결자가있는 바이트 배열입니다.

설명:

이 ‘인트로’를 위해 ‘바이트’와 ‘문자’라는 단어를 같은 의미로 사용합니다. 이렇게 배우는 것이 더 쉽기 때문입니다. 국제 문자를 처리하는 데 사용되는 다른 방법 (와이드 문자 및 다중 바이트 문자 시스템 ( 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;
}

즉, 컴파일러 szBufferprintf()in main()이 호출 될 때까지 사용 된 메모리를 해제했기 때문에 프로그램이 중단됩니다 . (컴파일러는 이러한 문제에 대해 미리 경고해야합니다.)

너무 쉽게 바 프지 않는 문자열을 반환하는 두 가지 방법이 있습니다.

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

두 번째 방법이 더 나은 이유는 여러 가지가 있습니다. 특히 다른 사람이 사용할 라이브러리를 작성하는 경우 (특정 할당 / 할당 해제 체계에 고정 할 필요가 없으며, 타사가 코드를 손상시킬 수 없습니다. 특정 메모리 관리 라이브러리에 연결할 필요는 없지만 모든 코드와 마찬가지로 가장 좋아하는 부분은 사용자에게 달려 있습니다. 이런 이유로 대부분의 사람들은 예 1을 선택하여 너무 많이 태워서 더 이상 그렇게 작성하는 것을 거부합니다.)

부인 성명:

나는 몇 년 전에 은퇴했고 내 C는 이제 약간 녹슬 었습니다. 이 데모 코드는 모두 C로 올바르게 컴파일되어야합니다 (모든 C ++ 컴파일러에서는 괜찮습니다).


답변

AC 문자열은 문자 배열에 대한 포인터로 정의됩니다.

포인터를 가질 수 없다면 정의상 문자열을 가질 수 없습니다.


답변

이 새로운 기능에 유의하십시오.

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

수정할 수있는 유일한 콘텐츠는 사용자가 소유 한 콘텐츠입니다. 또 다른 부작용-이 코드는 최소한 라이브러리 관점에서 보면 스레드로부터 안전합니다. 이 메서드를 호출하는 프로그래머는 사용 된 메모리 섹션이 스레드로부터 안전한지 확인해야합니다.


답변

문제는 함수의 반환 유형에 있습니다. 다음과 같아야합니다.

char *myFunction()

… 그러면 원래의 공식이 작동합니다.

당신이주의 할 수없는 어딘가에 라인을 따라, 포인터가 개입하지 않고 C 문자열이있다.

또한 : 컴파일러 경고를 표시하십시오. 명시 적 캐스트없이 a char *를 로 변환하는 리턴 라인에 대해 경고 했어야합니다 char.


답변

질문과 함께 새로 추가 된 배경 이야기를 기반으로 해당 월에 1에서 12까지의 정수를 반환하고 main () 함수가 switch 문 또는 if-else 래더를 사용하여 인쇄 할 내용을 결정하게하는 것은 어떻습니까? 확실히 가장 좋은 방법은 아니지만-char *가 될 것입니다-그러나 이와 같은 클래스의 맥락에서 나는 아마도 가장 우아하다고 생각합니다.


답변

기본 함수 인 호출자에서 배열을 만들고 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') */

그러나 포인터는 여전히 사용됩니다.


답변

함수 반환 유형은 단일 문자 ( char)입니다. 문자 배열의 첫 번째 요소에 대한 포인터를 반환해야합니다. 포인터를 사용할 수 없으면 망가진 것입니다. 🙁