2009-09-30 15 views
68

Próbuję zwrócić ciąg znaków C z funkcji, ale nie działa. Oto mój kod.Zwracanie łańcucha C z funkcji

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

W głównym Wołam go tak:

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

Próbowałem również kilka innych sposobów myFunction ale one nie działają. Np .:

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

Uwaga: nie wolno mi używać wskaźników!

Małe tło w tym problemie: Istnieje funkcja, która polega na ustaleniu, który miesiąc jest np. jeśli 1 to zwróci styczeń itd. itd.

Tak więc, kiedy ma się drukować, robi to tak. printf("Month: %s",calculateMonth(month));. Problem polega na tym, jak zwrócić ten ciąg z funkcji calculateMonth.

+8

Niestety * potrzebujesz * wskaźników w tym przypadku. –

+2

Dlaczego nie możesz używać wskaźników? –

+0

(musisz zwrócić int w main przy okazji) – user224579

Odpowiedz

148

podpis funkcja musi być:

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

Edit:

Tło:

Minęły lata od tego postu & nigdy nie myślałem, że będzie głosował w górę, ponieważ jest tak fundamentalny dla C & C++. Niemniej jednak należy przeprowadzić nieco więcej dyskusji.

W C (& C++), ciąg jest po prostu tablicą bajtów zakończonych bajtem zerowym - stąd termin "string-zero" jest używany do reprezentowania tego konkretnego smaku ciągu. Istnieją inne rodzaje łańcuchów, ale w C (& C++) ten smak jest z natury zrozumiały dla samego języka. Inne języki (Java, Pascal, itp.) Używają różnych metodologii, aby zrozumieć "mój ciąg".

Jeśli kiedykolwiek użyjesz Windows API (który jest w C++), zobaczysz dość regularnie parametry funkcji takie jak: "LPCSTR lpszName". Część "sz" reprezentuje to pojęcie "string-zero": tablica bajtów z terminatorem zerowym (/ zero).

Wyjaśnienie:

Dla tego „intra”, używam słowa „bajty” i „znaki” zamiennie, ponieważ łatwiej jest uczyć się w ten sposób. Należy pamiętać, że istnieją inne metody (szerokie znaki i wielobajtowe systemy znakowe - mbcs), które są używane w celu radzenia sobie ze znakami międzynarodowymi. UTF-8 jest przykładem mbcs. Ze względu na intro, po cichu "pomijam" to wszystko.

Pamięć:

Oznacza to, że ciąg jak "my string" rzeczywiście wykorzystuje 9 + 1 (= 10!) Bajtów. Ważne jest, aby wiedzieć, kiedy w końcu dostaniesz się do dynamicznego przydzielania ciągów. Tak więc, bez tego "zera kończącego", nie masz łańcucha. Masz w pamięci tablicę znaków (zwaną także buforem).

Longevity danych:

Zastosowanie funkcji w ten sposób:

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

... generalnie lądować Ci losowych nieobsłużonych-wyjątkami/usterek segmentu i tym podobne, zwłaszcza " w dół drogi'.

W skrócie, chociaż moja odpowiedź jest poprawna - 9 razy na 10 skończy się program, który ulega awarii, jeśli używasz go w ten sposób, szczególnie jeśli uważasz, że to "dobra praktyka", aby zrobić to w ten sposób . Krótko mówiąc: na ogół nie.

Na przykład, wyobraźmy sobie jakiś czas w przyszłości, ciąg musi zostać w jakiś sposób zmanipulowany. Generalnie, koder „będzie mieć prostą drogę” i (spróbuj) napisać kod jak poniżej:

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

Oznacza to, że program będzie katastrofy, ponieważ kompilator (może/nie może) wydali pamięć używaną przez szBuffer do czasu wywołania printf() w main(). (Twój kompilator powinien uprzednio ostrzec Cię przed takimi problemami).

Istnieją dwa sposoby zwracania ciągów, które nie będą tak łatwo barfować.

  1. zwracające bufory (statycznie lub dynamicznie przydzielane), które działają przez jakiś czas. W C++ użyj "klas pomocniczych" (np. std::string) do obsługi długowieczności danych (co wymaga zmiany wartości zwracanej przez funkcję), lub
  2. przekazania bufora do funkcji, która zostanie wypełniona informacjami.

Należy zauważyć, że niemożliwe jest użycie ciągów bez użycia wskaźników w C. Jak już pokazałem, są one synonimami. Nawet w C++ z klasami szablonów zawsze są używane bufory (np. Wskaźniki) w tle.

Aby lepiej odpowiedzieć na (teraz zmodyfikowane pytanie). (z pewnością można uzyskać wiele "innych odpowiedzi", które można zapewnić).

Bezpieczniejsze Odpowiedzi:

np 1. Korzystając statycznie przydzielone ciągi:

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

Co 'static' robi tutaj (wielu programistów nie lubią tego typu 'alokacja') jest to, że łańcuchy zostają umieszczone w segmencie danych programu. Oznacza to, że jest przydzielany na stałe.

Jeśli przejście na C++ użyjesz podobne strategie:

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

... ale to chyba łatwiejsze w użyciu klas pomocniczych, takich jak std::string, jeśli piszesz kod dla własnego używać (i nie być częścią biblioteki do dzielenia się z innymi).

np 2. Używanie rozmówcy zdefiniowane bufory:

jest to bardziej „głupi dowód” sposób przekazywania sznurki wokół. Zwrócone dane nie podlegają manipulacji ze strony wywołującej. Oznacza to, że np. 1 osoba może łatwo zostać wykorzystana przez stronę dzwoniącą i narazić Cię na błędy aplikacji. W ten sposób jest znacznie bezpieczniejsze (choć używa więcej linii kodu):

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

Istnieje wiele powodów, dlaczego 2nd metoda jest lepsza, zwłaszcza jeśli piszesz biblioteki do wykorzystania przez innych (don nie musi blokować twojego kodu, nie musisz łączyć się z określoną biblioteką zarządzania pamięcią), ale jak każdy kod, wszystko zależy od tego, co lubisz Najlepiej. Z tego powodu większość ludzi wybiera np.1 aż zostały one spalone tak wiele razy, że nie chcą, aby napisać to w ten sposób już;)

Zastrzeżenie:

I wycofał się kilka lat temu, a mój C jest teraz trochę zardzewiały. Ten kod demo powinien wszystko poprawnie skompilować z C (dla jakiegokolwiek kompilatora C++ jest w porządku).

+1

W rzeczywistości funkcja musi zwracać "char *", ponieważ literały łańcuchowe w C są typu 'char []'. Nie można ich jednak w żaden sposób modyfikować, dlatego preferowany jest powrót 'const char *' (patrz https://www.securecoding.cert.org/confluence/x/mwAV). Zwrot "char *" może być potrzebny, jeśli łańcuch będzie używany w starszej lub zewnętrznej funkcji biblioteki, która (niestety) oczekuje argumentu "char *" jako, nawet trudnego, że będzie tylko czytał z niego. C++, z drugiej strony, ma literały łańcuchowe typu "const char []" (a, od C++ 11, możesz także mieć literały 'std :: string'). – TManhente

+0

Punkt widzenia: zawsze nienawidziłem "Moje" w jakiejkolwiek formie kodu. Pokazuje brak myślenia o tym, kto jest właścicielem kodu i nie dodaje żadnych informacji o tym, co robi kod. Zawsze korzystałem z przysmaków prosto od uni, którzy mają ten opłakany nawyk. nawet "Some" jest dużo bardziej elegancki niż "Mój" – cmroanirgo

+15

@cmroanirgo prefiks * my * deklaruje czytelnikowi, że funkcja została utworzona przez użytkownika. Uważam, że jest to całkowicie uzasadnione do wykorzystania w takim kontekście. – arman

2

Twój typ funkcji zwraca pojedynczy znak. Powinieneś zwrócić wskaźnik do pierwszego elementu tablicy znaków. Jeśli nie możesz użyć wskaźników, to jesteś wkręcony. :(

+0

Nie wolno mi używać wskaźników: @ to jest problem. – itsaboutcode

7

Twój problem jest z typem zwracanej przez funkcję - musi być:

char *myFunction() 

... a potem oryginalny preparat zadziała

pamiętać, że nie możnamieć. C ciągi bez wskaźników, gdzieś na linii,

Ponadto: Podnieś swoje ostrzeżenia kompilatora, powinien był Cię ostrzec o tym powrocie, konwertując char * na char bez wyraźnej obsady.

+1

Myślę, że sygnatura powinna mieć char *, ponieważ ciąg jest dosłowny, ale jeśli się nie mylę, kompilator zaakceptuje to. – Luke

1

A char jest tylko pojedynczym jednobajtowym znakiem. Nie może przechowywać ciągu znaków, ani nie jest wskaźnikiem (którego najwyraźniej nie możesz mieć). Dlatego nie możesz rozwiązać problemu bez użycia wskaźników (dla których char[] jest cukrem syntaktycznym).

0

Twój prototyp funkcji stwierdza, że ​​twoja funkcja zwróci znak. Dlatego nie możesz zwrócić ciągu znaków w swojej funkcji.

11

Ciąg znaków C jest zdefiniowany jako wskaźnik do tablicy znaków.

Jeśli nie możesz mieć wskaźników, z definicji nie możesz mieć ciągów.

6

Uwaga ta nowa funkcja:

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

zdefiniowałem „tablicę” jako statyczne, inaczej kiedy koniec funkcja, zmienna (a wskaźnik wracasz) dostaje poza zakresem. Ponieważ pamięć jest przydzielana na stosie, to zostanie ona uszkodzona. Minusem tej implementacji jest to, że kod nie jest ponownie wprowadzany i nie jest bezpieczny dla wątków.

Inną opcją jest użycie malloc do przydzielenia ciągu znaków w stercie, a następnie zwolnienia we właściwych lokalizacjach kodu. Ten kod będzie ponownie zamknięty i bezpieczny dla wątków.

EDIT:

Jak zauważono w komentarzu, że jest to bardzo zła praktyka, ponieważ atakujący może następnie wstrzyknąć kod do swojej aplikacji (on musi otworzyć kod za pomocą gdb, a następnie dokonać przerwania i modyfikować wartość zwróconej zmiennej do przepełnienia i zabawa dopiero się zaczyna).

Jeśli jest o wiele bardziej zalecany, aby pozwolić rozmówcy obsługiwać alokacji pamięci. Zobacz ten nowy przykład:

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

Należy pamiętać, że jedyną treścią, którą można zmodyfikować, jest ta, którą użytkownik. Kolejny efekt uboczny - ten kod jest teraz bezpieczny dla wątków, przynajmniej z punktu widzenia biblioteki. Programista wywołujący tę metodę powinien sprawdzić, czy używana sekcja pamięci jest bezpieczna dla wątków.

+1

Jest to ogólnie zły sposób na rzeczy. Znakiem * znaków może manipulować otaczający kod. Oznacza to, że możesz wykonywać następujące czynności: strcpy (myFunction(), "Naprawdę długi ciąg"); a twój program ulegnie awarii z powodu naruszenia zasad dostępu. – cmroanirgo

1

Jeśli naprawdę nie można używać wskaźników, zrób coś takiego:

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

Magiczna liczba 9 jest okropne, to nie jest przykładem dobrego programowania. Ale rozumiesz. Zwróć uwagę, że wskaźniki i tablice są tym samym (trochę), więc to trochę oszustwo.

Mam nadzieję, że to pomoże!

+2

Używa wskaźnika w owczej skórze! : P – Twisol

+1

Zliczam dokładnie trzy podexpressions wskaźnik w tym kodzie. – caf

+0

Zwykle, jeśli chcesz wdrożyć takie rozwiązania w przypadku problemów z pracą domową, twoje wstępne założenia są błędne. – hrnt

5

Na podstawie nowo dodanej historii z pytaniem, dlaczego nie po prostu zwrócić liczbę całkowitą od 1 do 12 dla miesiąca, i niech funkcja main() używa instrukcji switch lub, jeśli-else drabina, aby zdecydować, co wydrukować ? Z pewnością nie jest to najlepsza droga - char * będzie - ale w kontekście takiej klasy wyobrażam sobie, że to chyba najbardziej elegancka.

+0

Masz rację, ja też myślę w tych kategoriach. – itsaboutcode

2

Albo jak o tym jednym:

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

I zadzwonić, że z miesiąca obliczyć gdzieś indziej.

Pozdrawiam,

Sebastiaan

+1

+1 nie o to, co zostało zadane przez OP, ale prawdopodobnie to zadanie oczekuje od ciebie, ponieważ nie może używać wskaźników. –

+0

Nawet printf używa wskaźników. Wskaźnik jest jak nóż - niezbędny do życia i pracy, ale musisz trzymać go za rączkę i używać ostrej strony do cięcia lub będziesz miał zły czas. Niefortunne umieszczanie spacji w definicji funkcji to błąd w mózgu dla wielu nowych programistów C. char * func (char * s); char * func (char * s); char * func * char * s); są wszystkie takie same, ale wszystkie wyglądają inaczej, a do złożonego zamieszania, * jest także operatorem de-reference dla zmiennych, które są wskaźnikami. –

1

Dobrze w kodzie próbujesz zwrócić String (w C który jest niczym innym NUL tablicę znaków), ale powrót typ swojej funkcji jest char powodującemu wszystkie kłopoty dla ciebie. Zamiast tego należy napisać to w ten sposób:

 
const char* myFunction() 
{ 

    return "My String"; 

} 

I to jest zawsze dobry, aby zakwalifikować się typ z const podczas przypisywania literały w C do wskaźników jako literały w C nie mogą być modyfikowane.

3

Możesz utworzyć tablicę w funkcji wywołującej, która jest główną funkcją, i przekazać tablicę do uczestnika, który jest twoją funkcją myFunction(). W ten sposób moja funkcja może wypełnić ciąg znaków w tablicy. Jednak trzeba zadeklarować myFunction() jako

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

i funkcji głównego myFunction powinny być nazywane w ten sposób

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

jednak wskaźnik jest nadal używany.

+0

Nie widzę sposobu, aby to zrobić bez użycia wskaźników. – ouflak

Powiązane problemy