Próbuję zwrócić ciąg C z funkcji, ale to nie działa. Oto mój kod.
char myFunction()
{
return "My String";
}
W main
nazywam to tak:
int main()
{
printf("%s", myFunction());
}
Próbowałem też innych sposobów myFunction
, ale one nie działają. Na przykład:
char myFunction()
{
char array[] = "my string";
return array;
}
Uwaga: nie wolno mi używać wskaźników!
Małe tło w tym problemie:
Jest funkcja, która sprawdza, który to miesiąc. Na przykład, jeśli jest 1, zwraca styczeń itd.
Więc kiedy to się dzieje, aby wydrukować, to robi to tak: printf("Month: %s",calculateMonth(month));
. Teraz problem polega na tym, jak zwrócić ten ciąg z calculateMonth
funkcji.
return 0
jest domyślna tylko w C99 (i C ++), ale nie w C90.Odpowiedzi:
Twój podpis funkcji musi być:
Tło:
Jest to tak fundamentalne dla C i C ++, ale trochę więcej dyskusji powinno być w porządku.
W C (i C ++ jeśli o to chodzi), łańcuch jest po prostu tablicą bajtów zakończoną bajtem zerowym - stąd termin „łańcuch zero” jest używany do reprezentowania tego szczególnego rodzaju ciągu. Istnieją inne rodzaje ciągów, ale w C (i C ++) ten smak jest z natury zrozumiały przez sam język. Inne języki (Java, Pascal itp.) Używają różnych metodologii do zrozumienia „mojego ciągu”.
Jeśli kiedykolwiek użyjesz Windows API (które jest w C ++), dość regularnie zobaczysz parametry funkcji, takie jak: "LPCSTR lpszName". Część „sz” reprezentuje pojęcie „string-zero”: tablica bajtów z zakończeniem zerowym (/ zero).
Wyjaśnienie:
Na potrzeby tego „wstępu” używam zamiennie słowa „bajty” i „znaki”, ponieważ w ten sposób łatwiej jest się nauczyć. Należy pamiętać, że istnieją inne metody (znaki szerokie i systemy znaków wielobajtowych ( mbcs )), które są używane do obsługi znaków międzynarodowych. UTF-8 to przykład pliku mbcs. Ze względu na intro po cichu „pomijam” to wszystko.
Pamięć:
Oznacza to, że ciąg taki jak „mój ciąg” w rzeczywistości wykorzystuje 9 + 1 (= 10!) Bajtów. Jest to ważne, aby wiedzieć, kiedy w końcu zajmiesz się dynamicznym przydzielaniem ciągów.
Tak więc bez tego „kończącego zera” nie masz łańcucha. Masz tablicę znaków (zwaną również buforem) wiszącą w pamięci.
Trwałość danych:
Korzystanie z funkcji w ten sposób:
... generalnie wyląduje z przypadkowymi nieobsługiwanymi wyjątkami / usterkami segmentów i tym podobnymi, zwłaszcza „w dół drogi”.
Krótko mówiąc, chociaż moja odpowiedź jest prawidłowa - 9 razy na 10 skończysz z programem, który ulega awarii, jeśli używasz go w ten sposób, zwłaszcza jeśli uważasz, że jest to „dobra praktyka”, aby robić to w ten sposób. Krótko mówiąc: generalnie nie.
Na przykład, wyobraź sobie kiedyś w przyszłości, że struną trzeba teraz w jakiś sposób manipulować. Ogólnie rzecz biorąc, programista `` pójdzie prostą ścieżką '' i (spróbuje) napisać kod w następujący sposób:
Oznacza to, że program będzie awarię ponieważ kompilator (może / nie może) wydali pamięć używaną przez
szBuffer
przez czasprintf()
wmain()
nazywa. (Twój kompilator powinien również wcześniej ostrzec Cię o takich problemach).Istnieją dwa sposoby zwracania łańcuchów, które nie będą tak łatwo barfować.
std::string
) do obsługi długowieczności danych (co wymaga zmiany wartości zwracanej przez funkcję) lubZauważ, że nie można używać łańcuchów bez użycia wskaźników w C. Jak pokazałem, są one synonimami. Nawet w C ++ z klasami szablonów zawsze w tle używane są bufory (czyli wskaźniki).
Tak więc, aby lepiej odpowiedzieć na (teraz zmodyfikowane pytanie). (Z pewnością istnieje wiele „innych odpowiedzi”, których można udzielić).
Bezpieczniejsze odpowiedzi:
Przykład 1, używając statycznie przydzielonych ciągów:
To, co robi tutaj „statyczny” (wielu programistów nie lubi tego typu „alokacji”), polega na tym, że ciągi znaków są umieszczane w segmencie danych programu. Oznacza to, że jest przydzielony na stałe.
Jeśli przejdziesz na C ++, zastosujesz podobne strategie:
... ale prawdopodobnie łatwiej jest korzystać z klas pomocniczych, na przykład
std::string
, jeśli piszesz kod na własny użytek (a nie część biblioteki, którą można udostępniać innym).Przykład 2, przy użyciu buforów zdefiniowanych przez wywołującego:
Jest to bardziej „niezawodny” sposób przekazywania sznurków. Zwrócone dane nie podlegają manipulacji ze strony dzwoniącego. Oznacza to, że przykład 1 może być łatwo nadużywany przez stronę dzwoniącą i narażać Cię na błędy aplikacji. W ten sposób jest dużo bezpieczniejszy (choć używa więcej linii kodu):
Istnieje wiele powodów, dla których ta druga metoda jest lepsza, szczególnie jeśli piszesz bibliotekę do użytku przez innych (nie musisz blokować określonego schematu alokacji / zwalniania, osoby trzecie nie mogą złamać twojego kodu, i nie musisz łączyć się z konkretną biblioteką zarządzania pamięcią), ale jak w przypadku każdego kodu, to od Ciebie zależy, co lubisz najbardziej. Z tego powodu większość ludzi wybiera na przykład 1, dopóki nie zostały spalone tyle razy, że nie chcą już pisać tego w ten sposób;)
Zrzeczenie się:
Kilka lat temu przeszedłem na emeryturę i moje C jest teraz trochę zardzewiałe. Ten kod demonstracyjny powinien w całości skompilować się poprawnie w C (jest jednak w porządku dla każdego kompilatora C ++).
źródło
char *
, ponieważ literały łańcuchowe w C są typuchar[]
. Nie wolno ich jednak w żaden sposób modyfikować, dlategoconst char*
preferowane jest zwracanie (patrz securecoding.cert.org/confluence/x/mwAV ). Zwrotchar *
może być potrzebny, jeśli ciąg zostanie użyty w starszej lub zewnętrznej funkcji bibliotecznej, która (niestety) oczekujechar*
argumentu jako argumentu, nawet jeśli będzie on tylko odczytywał z niego. Z drugiej strony C ++ ma literały łańcuchoweconst char[]
typu (a od C ++ 11 można również miećstd::string
literały).fraught with problems
w sekcji „Długowieczność danych” jest właściwie całkowicie poprawny. Literały ciągów mają statyczne okresy istnienia w C / C ++. Zobacz link, o którym wspomina Giorgi powyżej.Łańcuch AC jest zdefiniowany jako wskaźnik do tablicy znaków.
Jeśli nie możesz mieć wskaźników, z definicji nie możesz mieć łańcuchów.
źródło
void foo( char array[], int length)
. Oczywiściearray
jest to wskaźnik pod maską, ale nie jest to wskaźnik „jawnie”, dlatego może być bardziej intuicyjny dla kogoś, kto uczy się tablic, ale nie do końca nauczył się wskaźników.Zwróć uwagę na tę nową funkcję:
Zdefiniowałem „tablicę” jako statyczną. W przeciwnym razie po zakończeniu funkcji zmienna (i zwracany wskaźnik) wyjdzie poza zakres. Ponieważ ta pamięć jest przydzielona na stosie i zostanie uszkodzona. Wadą tej implementacji jest to, że kod nie jest ponownie wprowadzany i nie jest bezpieczny dla wątków.
Inną alternatywą byłoby użycie malloc do przydzielenia ciągu w stercie, a następnie zwolnienie we właściwych lokalizacjach kodu. Ten kod będzie ponownie wprowadzany i bezpieczny dla wątków.
Jak zauważono w komentarzu, jest to bardzo zła praktyka, ponieważ atakujący może następnie wstrzyknąć kod do Twojej aplikacji (musi otworzyć kod za pomocą GDB, następnie utworzyć punkt przerwania i zmodyfikować wartość zwracanej zmiennej, aby przepełnić i zabawa dopiero się zaczyna).
O wiele bardziej zalecane jest, aby wywołujący zajmował się alokacjami pamięci. Zobacz ten nowy przykład:
Należy pamiętać, że jedyną treścią, którą można zmodyfikować, jest treść użytkownika. Kolejny efekt uboczny - ten kod jest teraz bezpieczny wątkowo, przynajmniej z punktu widzenia biblioteki. Programista wywołujący tę metodę powinien sprawdzić, czy używana sekcja pamięci jest wątkowo bezpieczna.
źródło
Twój problem dotyczy zwracanego typu funkcji - musi to być:
... a wtedy Twoja oryginalna receptura zadziała.
Zauważ, że nie możesz mieć łańcuchów C bez wskaźników, które znajdują się gdzieś wzdłuż linii.
Ponadto: Podkręć ostrzeżenia kompilatora. Powinien był cię ostrzec o tej linii powrotnej konwertującej a
char *
nachar
bez jawnego rzutowania.źródło
Opierając się na nowo dodanej historii z pytaniem, dlaczego po prostu nie zwrócić liczby całkowitej od 1 do 12 dla miesiąca i pozwolić funkcji main () na użycie instrukcji switch lub drabiny if-else do podjęcia decyzji, co wydrukować? Z pewnością nie jest to najlepsza droga - byłby to char * - ale w kontekście takiej klasy jak ta wyobrażam sobie, że jest prawdopodobnie najbardziej elegancka.
źródło
Możesz utworzyć tablicę w funkcji wywołującej, która jest główną funkcją, i przekazać ją do obiektu wywoływanego, którym jest twoja funkcja myFunction (). W ten sposób myFunction może wypełnić ciąg znaków w tablicy. Musisz jednak zadeklarować myFunction () jako
A w głównej funkcji myFunction należy wywołać w ten sposób:
Jednak wskaźnik jest nadal używany.
źródło
Typ zwracanej funkcji to pojedynczy znak (
char
). Powinieneś zwrócić wskaźnik do pierwszego elementu tablicy znaków. Jeśli nie możesz użyć wskaźników, masz przerąbane. :(źródło
A co powiesz na ten:
I nazwij to miesiącem, który obliczasz gdzie indziej.
źródło
A
char
to tylko pojedynczy znak jednobajtowy. 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ć swojego problemu bez użycia wskaźników (cochar[]
jest cukrem syntaktycznym).źródło
Jeśli naprawdę nie możesz użyć wskaźników, zrób coś takiego:
Magiczna liczba 9 jest okropna, a to nie jest przykład dobrego programowania. Ale o co chodzi. Zauważ, że wskaźniki i tablice to to samo (coś w rodzaju), więc jest to trochę oszustwo.
źródło
Cóż, w swoim kodzie próbujesz zwrócić
String
(w C, który jest niczym innym jak tablicą znaków zakończoną znakiem null), ale typ zwracania twojej funkcji jest tym,char
który sprawia ci wszystkie kłopoty. Zamiast tego powinieneś napisać to w ten sposób:I zawsze dobrze jest zakwalifikować swój typ
const
przy przypisywaniu literałów w C do wskaźników, ponieważ literały w C nie są modyfikowalne.źródło
Prototyp funkcji stwierdza, że funkcja zwróci znak. W związku z tym nie możesz zwrócić ciągu w swojej funkcji.
źródło
W języku C literały ciągów są tablicami ze stałą statyczną klasą pamięci, więc zwrócenie wskaźnika do tej tablicy jest bezpieczne. Więcej szczegółów znajduje się w pytaniu o przepełnienie stosu „Czas życia” literału ciągu w C
źródło
Zwróć ciąg z funkcji
źródło