Bir işlevden bir C dizgesini döndürmek


109

Bir işlevden bir C dizesi döndürmeye çalışıyorum, ancak çalışmıyor. İşte kodum.

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

In mainben böyle sesleniyorum:

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

Bunun için başka yollar da denedim myFunctionama işe yaramıyorlar. Örneğin:

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

Not: İşaretçi kullanmama izin verilmiyor!

Bu problemle ilgili küçük arka plan:

Hangi ay olduğunu bulan fonksiyon var. Örneğin, 1 ise Ocak vb. Döndürür.

Yazdırmaya gidiyor Yani, böyle yapıyor: printf("Month: %s",calculateMonth(month));. Şimdi sorun, bu dizenin calculateMonthişlevden nasıl döndürüleceğidir .


10
Maalesef bu durumda işaretlere ihtiyacınız var .
Nick Bedford

1
@Hayato Burada yetişkin olduğumuza inanıyorum ve 0 döndürmesi gerektiğini biliyorum, sadece örnek lox vermek
içindi

3
return 0varsayılan olarak yalnızca C99'da (ve C ++ 'da) ancak C90'da belirtilmemiştir.
hrnt

1
Öyleyse, gerçekten sadece bozulmuş bir işaretçi manipülasyonu olan aptalca saldırıların yanı sıra bunu yapamayacaksınız. İşaretçilerin bir nedeni vardır ...: |
GManNickG

Yanıtlar:


222

İşlev imzanız şu şekilde olmalıdır:

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

Arka fon:

C & C ++ için çok önemlidir, ancak biraz daha fazla tartışma sırayla yapılmalıdır.

C'de (& C ++ bu konuda), bir dize sadece bir sıfır bayt ile sonlandırılmış bir bayt dizisidir - bu nedenle "dize-sıfır" terimi, bu özel dizi çeşidini temsil etmek için kullanılır. Başka tür dizgiler de vardır, ancak C (& C ++) 'da, bu tat doğal olarak dilin kendisi tarafından anlaşılır. Diğer diller (Java, Pascal, vb.) "Dizimi" anlamak için farklı metodolojiler kullanır.

Windows API'yi (C ++ içinde) kullanırsanız, "LPCSTR lpszName" gibi oldukça düzenli olarak işlev parametreleri görürsünüz. 'Sz' bölümü, bu 'dizge-sıfır' kavramını temsil eder: boş (/ sıfır) sonlandırıcıya sahip bir bayt dizisi.

Açıklama:

Bu 'giriş' uğruna, 'bayt' ve 'karakterler' kelimelerini birbirinin yerine kullanıyorum, çünkü bu şekilde öğrenmek daha kolay. Uluslararası karakterlerle başa çıkmak için kullanılan başka yöntemler (geniş karakterler ve çok baytlı karakter sistemleri ( mbcs )) olduğunu unutmayın. UTF-8 bir mbcs örneğidir. Giriş uğruna, tüm bunları sessizce 'atlıyorum'.

Hafıza:

Bu, "dizem" gibi bir dizenin aslında 9 + 1 (= 10!) Bayt kullandığı anlamına gelir. Bu, dizeleri dinamik olarak ayırmaya ne zaman başladığını bilmek önemlidir.

Yani, bu 'sonlandırıcı sıfır' olmadan, bir dizeniz olmaz. Hafızada asılı duran bir dizi karakteriniz (tampon da denir) var.

Verilerin uzun ömürlü olması:

İşlevin bu şekilde kullanılması:

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

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

... sizi genellikle rastgele ele alınmayan istisnalar / segment hataları ve benzerleriyle, özellikle de 'yolun aşağısında' yönlendirir.

Kısacası, cevabım doğru olsa da - 10'da 9'u, bu şekilde kullanırsanız, özellikle de bu şekilde yapmanın 'iyi bir uygulama' olduğunu düşünüyorsanız, çöken bir programla karşılaşacaksınız. Kısaca: Genelde değil.

Örneğin, gelecekte bir zaman düşünün, dizginin artık bir şekilde manipüle edilmesi gerekiyor. Genel olarak, bir kodlayıcı 'kolay yolu seçer' ve şu şekilde kod yazmaya (dener):

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

Derleyici (/ veya olmayabilir) tarafından kullanılan bellek serbest etmelerinden Yani, programınız çökmesine olacak szBufferzaman printf()içinde main()denir. (Derleyiciniz sizi bu tür sorunlar konusunda önceden uyarmalıdır.)

Bu kadar kolay kusmayacak dizeleri döndürmenin iki yolu vardır.

  1. bir süre yaşayan tamponları (statik veya dinamik olarak ayrılmış) döndürür. C ++ ' std::stringda verilerin uzun ömürlülüğünü işlemek için' yardımcı sınıfları '(örneğin ) kullanın (bu, işlevin dönüş değerini değiştirmeyi gerektirir) veya
  2. bilgiyle doldurulan işleve bir tampon iletir.

C'de işaretçiler kullanmadan dizeleri kullanmanın imkansız olduğuna dikkat edin. Gösterdiğim gibi, bunlar eşanlamlıdır. C ++ şablon sınıflarında bile arka planda her zaman kullanılan tamponlar (yani işaretçiler) vardır.

Yani, (şimdi değiştirilmiş soruya) daha iyi cevap vermek için. (Verilebilecek çeşitli 'diğer yanıtlar' olduğundan emin olabilirsiniz.)

Daha Güvenli Cevaplar:

Örnek 1, statik olarak ayrılmış dizeleri kullanarak:

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

Burada 'durağan' olan şey (birçok programcı bu tür 'ayırma'yı sevmez), dizelerin programın veri segmentine yerleştirilmesidir. Yani kalıcı olarak tahsis edilmiştir.

C ++ 'ya geçerseniz, benzer stratejiler kullanırsınız:

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

... ancak std::string, kodu kendi kullanımınız için yazıyorsanız (ve başkalarıyla paylaşılacak bir kitaplığın parçası değil) , yardımcı sınıfları kullanmak muhtemelen daha kolaydır .

Örnek 2, arayan tanımlı arabellekleri kullanarak:

Bu, ipleri etrafta dolaştırmanın daha 'kusursuz' yoludur. Döndürülen veriler, arayan tarafın müdahalesine tabi değildir. Yani, örnek 1 arayan taraf tarafından kolaylıkla kötüye kullanılabilir ve sizi uygulama hatalarına maruz bırakabilir. Bu şekilde, çok daha güvenlidir (daha fazla kod satırı kullansa da):

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

İkinci yöntemin daha iyi olmasının birçok nedeni vardır, özellikle başkaları tarafından kullanılmak üzere bir kitaplık yazıyorsanız (belirli bir tahsis / serbest bırakma şemasına kilitlenmeniz gerekmez, üçüncü taraflar kodunuzu kıramazlar, ve belirli bir bellek yönetimi kitaplığına bağlanmanıza gerek yoktur), ancak tüm kodlar gibi, en çok neyi seveceğiniz size bağlıdır. Bu nedenle, çoğu insan örneğin 1'i o kadar çok kez yanana kadar seçer ki artık o şekilde yazmayı reddederler;)

Yasal Uyarı:

Birkaç yıl önce emekli oldum ve şimdi C'm biraz paslanmış. Bu demo kodunun tümü C ile düzgün bir şekilde derlenmelidir (yine de herhangi bir C ++ derleyicisi için uygundur).


2
Aslında, char *C'deki dizge değişmezleri türünde olduğu için işlevin a döndürmesi gerekir char[]. Bununla birlikte, herhangi bir şekilde değiştirilmemelidirler, bu nedenle geri dönüş const char*tercih edilir (bkz. Securecoding.cert.org/confluence/x/mwAV ). char *Dizge, (maalesef) char*bağımsız değişken olarak bekleyen , hatta yalnızca ondan okuyacak olsa da, eski veya harici bir kitaplık işlevinde kullanılacaksa, geri döndürme gerekli olabilir . Öte yandan C ++, const char[]türde dize değişmez değerlerine sahiptir (ve C ++ 11'den beri std::stringdeğişmez değerlere de sahip olabilirsiniz ).
TManhente

17
@cmroanirgo benim önek işlevi kullanıcı tarafından oluşturulduğu okuyucuya beyan eder. Böyle bir bağlamda kullanmayı son derece makul buluyorum.
Quant


6
fraught with problems"Verilerin uzun ömürlülüğü" bölümünde işaretlenen kod , aslında tamamen geçerlidir. Dize değişmezleri C / C ++ 'da statik yaşam sürelerine sahiptir. Giorgi'nin yukarıda bahsettiği bağlantıya bakın.
chengiz

1
@cmroanirgo Dize değişmezlerini döndürmek iyi bir uygulamadır ve iyi bir stildir. "Sorunlarla dolu" değil ve 10 seferin 9'unu çökmeyecek: Asla çökmeyecek. 80'lerden derleyiciler bile (en azından benim kullandıklarım) sınırsız dize değişmezlerinin ömrünü doğru bir şekilde destekliyor. Not: Yanıtı düzenlemekle ilgili ne demek istediğinden emin değilim: Hâlâ çökmeye eğilimli olduğunu söylediğini görüyorum.
2017

12

AC dizesi, bir karakter dizisine işaretçi olarak tanımlanır.

İşaretçileriniz yoksa, tanım gereği dizeleriniz olamaz.


Bir işleve bir dizide geçmek ve bu dizi üzerinde çalışır sonra edebilirsiniz: void foo( char array[], int length). Elbette array, başlığın altında bir işaretçi, ancak "açıkça" bir işaretçi değil ve bu nedenle dizileri öğrenen, ancak işaretçileri tam olarak öğrenmemiş biri için daha sezgisel olabilir .
jvriesem

12

Bu yeni işlevi not alın:

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

"Dizi" yi statik olarak tanımladım. Aksi takdirde, fonksiyon sona erdiğinde, değişken (ve geri döndüğünüz işaretçi) kapsam dışına çıkar. O belleğin yana yığın tahsis edilir ve bu edecektir bozuk olsun. Bu uygulamanın dezavantajı, kodun evresel olmaması ve evreli olmamasıdır.

Diğer bir alternatif , dizeyi yığın içinde ayırmak için malloc kullanmak ve ardından kodunuzun doğru konumlarında serbest bırakmaktır. Bu kod evresel ve iş parçacığı güvenli olacaktır.

Yorumda belirtildiği gibi, bu çok kötü bir uygulamadır, çünkü daha sonra bir saldırgan uygulamanıza kod enjekte edebilir (kodu GDB kullanarak açması, ardından bir kesme noktası oluşturması ve döndürülen bir değişkenin değerini taşma ve eğlence yeni başlıyor).

Arayanın bellek ayırmaları hakkında işlem yapmasına izin verilmesi çok daha tavsiye edilir. Şu yeni örneğe bakın:

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

Değiştirilebilecek tek içeriğin kullanıcının içeriği olduğuna dikkat edin. Başka bir yan etki - bu kod, en azından kütüphane açısından artık iş parçacığı açısından güvenlidir. Bu yöntemi çağıran programcı, kullanılan bellek bölümünün evrelerin güvenli olduğunu doğrulamalıdır.


2
Bu genellikle işleri halletmenin kötü bir yoludur. Char * çevreleyen kod tarafından değiştirilebilir. Yani, şöyle şeyler yapabilirsiniz: strcpy (myFunction (), "Gerçekten uzun bir dizge"); ve programınız erişim ihlali nedeniyle çökecektir.
cmroanirgo

"Kullanıcının" yakınında bir şey eksik .
Peter Mortensen

8

Sizin sorununuz fonksiyonun dönüş türüyle ilgili - şu olmalı:

char *myFunction()

... ve sonra orijinal formülasyonunuz işe yarayacaktır.

Çizgi boyunca bir yerde işaretçiler dahil olmadan C dizelerine sahip olamayacağınızı unutmayın .

Ayrıca: Derleyici uyarılarınızı açın. Bu bir dönüştürme o geri dönüş hattı konuda uyarmıştım gerekirdi char *için charaçık bir döküm olmadan.


1
Dize değişmez olduğu için imzanın char * oluşturması gerektiğini düşünüyorum, ancak yanılmıyorsam derleyici bunu kabul edecektir.
Luke

5

Soruyla birlikte yeni eklenen geçmişinize dayanarak, neden ay için 1'den 12'ye kadar bir tamsayı döndürüp main () işlevinin neyi yazdıracağına karar vermek için bir switch ifadesi veya if-else merdiven kullanmasına izin vermiyorsunuz? Kesinlikle gitmek için en iyi yol değil - char * olurdu - ama böyle bir sınıf bağlamında bunun muhtemelen en zarif olduğunu düşünüyorum.


3

Arayanda ana işlev olan diziyi yaratabilir ve diziyi myFunction () işleviniz olan aranan uca iletebilirsiniz. Böylece, myFunction dizeyi diziye doldurabilir. Ancak, myFunction () işlevini şu şekilde bildirmeniz gerekir:

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

Ve ana işlevde, myFunction şu şekilde çağrılmalıdır:

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

Ancak yine de bir işaretçi kullanılmaktadır.


2

İşlev dönüş türünüz tek bir karakterdir ( char). Karakter dizisinin ilk öğesine bir gösterici döndürmelisiniz. İşaretçileri kullanamazsan, mahvolursun. :(


2

Ya da buna ne dersin:

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

Ve bunu başka bir yerde hesaplama yaptığınız ay olarak adlandırın.


1
OP'nin sorduğu şeyi + 1'leyin, ancak bu muhtemelen görevin sizden beklediği şeydir, çünkü o işaretçileri kullanamaz.
Vitim.us

Printf bile işaretçiler kullanır. Bir işaretçi bıçak gibidir - yaşamak ve çalışmak için gereklidir, ancak onu sapından tutmalı ve kesmek için keskin tarafını kullanmalısın yoksa kötü vakit geçireceksin. İşlev tanımında boşlukların talihsiz bir şekilde yerleştirilmesi, birçok yeni C programcısı için bir beyin hatasıdır. char * func (char * s); karakter işlevi (karakter * s); char func * char * s); hepsi aynıdır, ancak hepsi farklı görünür ve karışıklığı birleştirmek için * aynı zamanda işaretçi olan değişkenler için de-referans operatörüdür.
Chris Reid

1

A char, yalnızca tek baytlık bir karakterdir. Karakter dizisini saklayamaz, ne de bir işaretçi (görünüşe göre sahip olamayacağınız). Bu nedenle, sorununuzu işaretçiler ( char[]sözdizimsel şeker) kullanmadan çözemezsiniz .


1

İşaretçileri gerçekten kullanamıyorsanız, şöyle bir şey yapın:

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

9 sihirli sayı berbat ve bu iyi programlamaya bir örnek değil. Ama anladın. İşaretçilerin ve dizilerin aynı şey olduğuna dikkat edin (bir çeşit), bu yüzden bu biraz hile.


Genellikle, ev ödevi problemlerine bu tür çözümler uygulamanız gerekirse, ön varsayımlarınız yanlıştır.
hrnt

1

Eh, kodunuzda a döndürmeye çalışıyorsunuz String(C'de boş sonlandırılmış bir karakter dizisinden başka bir şey değildir), ancak işlevinizin dönüş türü sizin chariçin tüm sorunlara neden oluyor. Bunun yerine şu şekilde yazmalısınız:

const char* myFunction()
{

    return "My String";

}

Ve constC'deki değişmez değerleri işaretçiler için atarken türünüzü nitelemek her zaman iyidir çünkü C'deki değişmez değerler değiştirilebilir değildir.


0

İşlev prototipiniz, işlevinizin bir karakter döndüreceğini belirtir. Bu nedenle, işlevinizde bir dize döndüremezsiniz.



0

İşlevden dizge döndür

#include <stdio.h>

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

int main(void) {
  printf("%s", greet());
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.