Mengembalikan string C dari fungsi

109

Saya mencoba mengembalikan string C dari suatu fungsi, tetapi tidak berfungsi. Ini kode saya.

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

Dalam mainsaya menyebutnya seperti ini:

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

Saya juga telah mencoba beberapa cara lain myFunction, tetapi tidak berhasil. Sebagai contoh:

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

Catatan: Saya tidak diperbolehkan menggunakan pointer!

Sedikit latar belakang tentang masalah ini:

Ada fungsi yang mencari tahu bulan apa ini. Misalnya, jika 1 maka itu mengembalikan Januari, dll.

Jadi ketika akan mencetak, itu melakukan seperti ini: printf("Month: %s",calculateMonth(month));. Sekarang masalahnya adalah bagaimana mengembalikan string itu dari calculateMonthfungsi.

itsaboutcode
sumber
10
Sayangnya Anda membutuhkan petunjuk dalam kasus ini.
Nick Bedford
1
@Hayato Ya saya percaya kami adalah orang dewasa di sini dan tahu itu harus mengembalikan 0, itu hanya untuk memberi contoh lox ..
itsaboutcode
3
return 0tersirat secara default hanya di C99 (dan C ++) tetapi tidak di C90.
hrnt
1
Maka Anda tidak akan bisa melakukannya, di samping peretasan bodoh yang sebenarnya hanya merusak manipulasi penunjuk. Pointer ada karena suatu alasan ...: |
GManNickG

Jawaban:

222

Tanda tangan fungsi Anda harus:

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

Latar Belakang:

Ini sangat mendasar untuk C & C ++, tetapi sedikit lebih banyak diskusi harus dilakukan.

Dalam C (& C ++ dalam hal ini), string hanyalah array byte yang diakhiri dengan byte nol - maka istilah "string-nol" digunakan untuk mewakili rasa string tertentu ini. Ada jenis string lain, tetapi di C (& C ++), rasa ini secara inheren dipahami oleh bahasa itu sendiri. Bahasa lain (Java, Pascal, dll.) Menggunakan metodologi berbeda untuk memahami "string saya".

Jika Anda pernah menggunakan Windows API (yang ada di C ++), Anda akan melihat parameter fungsi yang cukup teratur seperti: "LPCSTR lpszName". Bagian 'sz' merepresentasikan gagasan 'string-zero' ini: array byte dengan terminator null (/ nol).

Klarifikasi:

Demi 'intro' ini, saya menggunakan kata 'bytes' dan 'characters' secara bergantian, karena cara ini lebih mudah dipelajari. Ketahuilah bahwa ada metode lain (karakter lebar, dan sistem karakter multi-byte ( mbcs )) yang digunakan untuk menangani karakter internasional. UTF-8 adalah contoh mbcs. Demi intro, saya diam-diam 'melewatkan' semua ini.

Penyimpanan:

Ini berarti bahwa string seperti "string saya" sebenarnya menggunakan 9 + 1 (= 10!) Byte. Hal ini penting untuk diketahui saat Anda akhirnya bisa mengalokasikan string secara dinamis.

Jadi, tanpa 'penghentian nol' ini, Anda tidak memiliki string. Anda memiliki serangkaian karakter (juga disebut buffer) yang berkeliaran di memori.

Data umur panjang:

Penggunaan fungsinya seperti ini:

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

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

... umumnya akan membuat Anda mendapatkan kesalahan acak tidak tertangani / segmen dan sejenisnya, terutama 'di jalan'.

Singkatnya, meskipun jawaban saya benar - 9 kali dari 10 Anda akan mendapatkan program yang macet jika Anda menggunakannya seperti itu, terutama jika menurut Anda 'praktik yang baik' untuk melakukannya dengan cara itu. Singkatnya: Biasanya tidak.

Misalnya, bayangkan suatu saat nanti, string sekarang perlu dimanipulasi dengan cara tertentu. Umumnya, pembuat kode akan 'mengambil jalan yang mudah' dan (mencoba) menulis kode seperti ini:

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

Artinya, program Anda akan macet karena kompiler (mungkin / mungkin tidak) telah melepaskan memori yang digunakan szBufferpada saat printf()in main()dipanggil. (Kompiler Anda juga harus memperingatkan Anda tentang masalah seperti itu sebelumnya.)

Ada dua cara untuk mengembalikan string yang tidak akan mudah muntah.

  1. mengembalikan buffer (statis atau dialokasikan secara dinamis) yang aktif untuk sementara waktu. Dalam C ++ gunakan 'kelas pembantu' (misalnya, std::string) untuk menangani umur panjang data (yang memerlukan perubahan nilai kembalian fungsi), atau
  2. meneruskan buffer ke fungsi yang diisi dengan informasi.

Perhatikan bahwa tidak mungkin menggunakan string tanpa menggunakan pointer di C. Seperti yang telah saya tunjukkan, string itu sama. Bahkan di C ++ dengan kelas template, selalu ada buffer (yaitu pointer) yang digunakan di latar belakang.

Jadi, untuk menjawab dengan lebih baik (pertanyaan yang sekarang dimodifikasi). (Pasti ada berbagai 'jawaban lain' yang dapat diberikan.)

Jawaban yang Lebih Aman:

Contoh 1, menggunakan string yang dialokasikan secara statis:

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

Apa yang dilakukan 'statis' di sini (banyak pemrogram tidak menyukai jenis 'alokasi' ini) adalah string dimasukkan ke dalam segmen data program. Artinya, dialokasikan secara permanen.

Jika Anda pindah ke C ++, Anda akan menggunakan strategi serupa:

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

... tapi mungkin lebih mudah menggunakan kelas helper, seperti std::string, jika Anda menulis kode untuk Anda gunakan sendiri (dan bukan bagian dari pustaka untuk dibagikan dengan orang lain).

Contoh 2, menggunakan buffer yang ditentukan pemanggil:

Ini adalah cara yang lebih 'sangat mudah' untuk menyebarkan string. Data yang dikembalikan tidak tunduk pada manipulasi oleh pihak yang menelepon. Artinya, contoh 1 dapat dengan mudah disalahgunakan oleh pihak yang menelepon dan membuat Anda terkena kesalahan aplikasi. Dengan cara ini, jauh lebih aman (meskipun menggunakan lebih banyak baris kode):

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

Ada banyak alasan mengapa metode kedua lebih baik, terutama jika Anda menulis perpustakaan untuk digunakan oleh orang lain (Anda tidak perlu mengunci skema alokasi / deallokasi tertentu, pihak ketiga tidak dapat memecahkan kode Anda, dan Anda tidak perlu menautkan ke pustaka manajemen memori tertentu), tetapi seperti semua kode, terserah Anda tentang apa yang paling Anda sukai. Oleh karena itu, kebanyakan orang memilih misalnya 1 sampai mereka terbakar berkali-kali sehingga mereka menolak untuk menulis seperti itu lagi;)

Penolakan:

Saya pensiun beberapa tahun yang lalu dan nilai C saya agak berkarat sekarang. Kode demo ini harus dikompilasi dengan baik dengan C (meskipun demikian, tidak masalah untuk kompiler C ++).

cmroanirgo.dll
sumber
2
Sebenarnya, fungsi tersebut perlu mengembalikan a char *, karena string literal di C memiliki tipe char[]. Namun, mereka tidak boleh dimodifikasi dengan cara apa pun, jadi const char*lebih disukai mengembalikan (lihat securecoding.cert.org/confluence/x/mwAV ). Pengembalian char *mungkin diperlukan jika string akan digunakan dalam fungsi pustaka warisan atau eksternal yang (sayangnya) mengharapkan char*argumen sebagai, bahkan meskipun itu hanya akan membacanya. C ++, sebaliknya, memiliki literal string const char[]bertipe (dan, karena C ++ 11, Anda juga dapat memiliki std::stringliteral).
TManhente
17
@cmroanirgo, awalan saya menyatakan kepada pembaca bahwa fungsi tersebut dibuat oleh pengguna. Saya merasa sangat masuk akal untuk digunakan dalam konteks seperti itu.
Kuantitas
4
menurut di sini: stackoverflow.com/questions/9970295/… , Anda dapat mengembalikan string literal
giorgim
6
Kode yang ditandai fraught with problemsdi bagian "Umur panjang data" sebenarnya benar-benar valid. Literal string memiliki masa pakai statis di C / C ++. Lihat tautan yang disebutkan Giorgi di atas.
chengiz
1
@cmroanirgo Mengembalikan literal string adalah praktik yang baik, dan gaya yang baik. Ini tidak "penuh dengan masalah", dan tidak akan crash 9 dari 10 kali: Ini tidak akan pernah crash. Bahkan kompiler dari tahun 80-an (setidaknya yang saya gunakan) dengan benar mendukung masa pakai string literal yang tidak terbatas. Catatan: Saya tidak yakin apa yang Anda maksud dengan mengedit jawaban: Saya masih melihatnya mengatakan itu rawan crash.
berakhir
12

String AC didefinisikan sebagai penunjuk ke larik karakter.

Jika Anda tidak dapat memiliki pointer, menurut definisi Anda tidak dapat memiliki string.

Crashworks
sumber
Anda dapat lulus dalam sebuah array ke fungsi dan kemudian beroperasi pada array bahwa: void foo( char array[], int length). Tentu saja, arrayini adalah penunjuk di bawah tenda, tetapi ini bukan penunjuk "secara eksplisit", dan oleh karena itu mungkin lebih intuitif bagi seseorang yang mempelajari array tetapi yang belum terlalu mempelajari penunjuk.
jvriesem
12

Perhatikan fungsi baru ini:

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

Saya mendefinisikan "array" sebagai statis. Jika tidak, saat fungsi berakhir, variabel (dan penunjuk yang Anda kembalikan) keluar dari ruang lingkup. Karena memori itu dialokasikan pada tumpukan, dan itu akan rusak. Kelemahan dari implementasi ini adalah kode tersebut tidak reentrant dan tidak threadsafe.

Alternatif lain adalah menggunakan malloc untuk mengalokasikan string di heap, lalu membebaskan di lokasi yang benar dari kode Anda. Kode ini akan menjadi reentrant dan threadsafe.

Seperti disebutkan dalam komentar, ini adalah praktik yang sangat buruk, karena penyerang kemudian dapat menyuntikkan kode ke aplikasi Anda (dia perlu membuka kode menggunakan GDB, lalu membuat breakpoint dan mengubah nilai variabel yang dikembalikan menjadi overflow dan kesenangan baru saja dimulai).

Jauh lebih disarankan untuk membiarkan pemanggil menangani tentang alokasi memori. Lihat contoh baru ini:

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

Perhatikan bahwa satu-satunya konten yang dapat dimodifikasi adalah konten pengguna. Efek samping lain - kode ini sekarang threadsafe, setidaknya dari sudut pandang perpustakaan. Pemrogram yang memanggil metode ini harus memverifikasi bahwa bagian memori yang digunakan aman untuk thread.

elcuco
sumber
2
Ini umumnya cara yang buruk untuk melakukan sesuatu. Karakter * dapat dimanipulasi oleh kode sekitarnya. Artinya, Anda dapat melakukan hal-hal seperti ini: strcpy (myFunction (), "A really long string"); dan program Anda akan macet karena pelanggaran akses.
cmroanirgo
Ada sesuatu yang hilang di dekat "salah satu pengguna" .
Peter Mortensen
8

Masalah Anda adalah dengan jenis fungsi yang dikembalikan - itu harus:

char *myFunction()

... dan kemudian formulasi asli Anda akan berhasil.

Perhatikan bahwa Anda tidak dapat memiliki string C tanpa melibatkan pointer, di suatu tempat di sepanjang baris.

Juga: Nyalakan peringatan kompiler Anda. Ini harus telah memperingatkan Anda tentang itu jalur kembali mengkonversi char *ke chartanpa cast yang eksplisit.

kafe
sumber
1
Saya pikir tanda tangan harus const char * karena string adalah literal tetapi jika saya tidak salah kompilator akan menerima ini.
Luke
5

Berdasarkan backstory yang baru Anda tambahkan dengan pertanyaan, mengapa tidak mengembalikan integer dari 1 hingga 12 untuk bulan tersebut, dan biarkan fungsi main () menggunakan pernyataan switch atau tangga if-else untuk memutuskan apa yang akan dicetak? Ini tentu bukan cara terbaik untuk pergi - char * akan - tetapi dalam konteks kelas seperti ini, saya membayangkan itu mungkin yang paling elegan.

Twisol
sumber
3

Anda bisa membuat larik di pemanggil, yang merupakan fungsi utama, dan meneruskan larik ke callee yang merupakan myFunction () Anda. Dengan demikian myFunction dapat mengisi string ke dalam array. Namun, Anda perlu mendeklarasikan myFunction () sebagai

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

Dan di fungsi utama, myFunction harus dipanggil dengan cara ini:

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') */

Namun, penunjuk masih digunakan.

ChainLooper
sumber
2

Jenis pengembalian fungsi Anda adalah karakter tunggal ( char). Anda harus mengembalikan pointer ke elemen pertama dari larik karakter. Jika Anda tidak dapat menggunakan pointer, maka Anda sedang kacau. :(

hrnt
sumber
2

Atau bagaimana dengan yang ini:

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

Dan sebut itu dengan bulan Anda menghitung di tempat lain.

Sebastiaan M
sumber
1
+1 bukan apa yang diminta OP tetapi ini mungkin tugas yang diharapkan untuk Anda lakukan, karena dia tidak dapat menggunakan pointer.
Vitim.us
Bahkan printf menggunakan pointer. Penunjuk seperti pisau - penting untuk hidup dan bekerja, tetapi Anda harus memegang gagangnya dan menggunakan sisi yang tajam untuk memotong atau Anda akan mengalami saat-saat yang buruk. Penempatan spasi yang tidak menguntungkan dalam definisi fungsi adalah kesalahan otak bagi banyak programmer C baru. char * func (char * s); char func (char * s); char func * char * s); semuanya sama tetapi semuanya terlihat berbeda, dan untuk menambah kebingungan, * juga merupakan operator de-referensi untuk variabel yang merupakan pointer.
Chris Reid
1

A charhanya satu karakter satu byte. Itu tidak dapat menyimpan string karakter, juga bukan penunjuk (yang tampaknya tidak dapat Anda miliki). Oleh karena itu, Anda tidak dapat menyelesaikan masalah Anda tanpa menggunakan pointer (yang char[]merupakan gula sintaksis).

Nick Bedford
sumber
1

Jika Anda benar-benar tidak dapat menggunakan pointer, lakukan sesuatu seperti ini:

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

Angka ajaib 9 itu buruk, dan ini bukan contoh pemrograman yang bagus. Tapi Anda mengerti maksudnya. Perhatikan bahwa pointer dan array adalah hal yang sama (sejenis), jadi ini sedikit curang.

Sebastiaan M
sumber
Biasanya jika Anda perlu menerapkan solusi seperti itu untuk masalah pekerjaan rumah, maka asumsi awal Anda salah.
jam
1

Nah, dalam kode Anda, Anda mencoba untuk mengembalikan String(dalam C yang tidak lain adalah array karakter yang diakhiri dengan null), tetapi jenis kembalian dari fungsi Anda charyang menyebabkan semua masalah bagi Anda. Sebaliknya Anda harus menulis seperti ini:

const char* myFunction()
{

    return "My String";

}

Dan selalu bagus untuk memenuhi syarat tipe Anda constsaat menetapkan literal dalam C ke pointer karena literal dalam C tidak dapat dimodifikasi.

Cheshar
sumber
0

Prototipe fungsi Anda menyatakan bahwa fungsi Anda akan mengembalikan karakter. Jadi, Anda tidak bisa mengembalikan string dalam fungsi Anda.

pengguna224579
sumber
0
char* myFunction()
{
    return "My String";
}

Di C, literal string adalah array dengan kelas memori konstan statis, jadi mengembalikan pointer ke array ini aman. Lebih detail ada di pertanyaan Stack Overflow "Life-time" dari string literal di C

Oleg Karavan
sumber
0

Kembalikan string dari fungsi

#include <stdio.h>

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

int main(void) {
  printf("%s", greet());
}
passionatedevops
sumber