C'deki bir işlevden bir "yapı" döndürme


171

Bugün birkaç arkadaşa C'lerin nasıl kullanılacağını öğretiyordum struct. Onlardan biri, structyanıtladığım bir işlevden bir geri dönüp dönemeyeceğinizi sordu: "Hayır! Bunun yerine dinamik olarak malloced structs'ye işaretçiler döndürürdünüz ."

Öncelikle C ++ yapan birinden geliyor, structdeğerlerle s döndüremediğini bekliyordum . C ++ 'da operator =nesneleriniz için aşırı yükleyebilirsiniz ve nesnenizi değere göre döndürecek bir işleve sahip olmak tam mantıklıdır. Bununla birlikte, C'de bu seçeneğe sahip değilsiniz ve bu yüzden derleyicinin gerçekte ne yaptığını düşünmemi sağladı. Aşağıdakileri göz önünde bulundur:

struct MyObj{
    double x, y;
};

struct MyObj foo(){
    struct MyObj a;

    a.x = 10;
    a.y = 10;

    return a;
}        

int main () {

    struct MyObj a;

    a = foo();    // This DOES work
    struct b = a; // This does not work

    return 0;
}    

Neden struct b = a;işe yaramayacağını anlıyorum - operator =veri türünüz için aşırı yükleme yapamazsınız . Nasıl a = foo();derlenir? Bunun dışında bir şey mi ifade ediyor struct b = a;? Belki de sorulması gereken soru şudur: İmzalama returnile birlikte ifade tam olarak ne =yapıyor?

[değiştir]: Tamam, ben sadece struct b = abir sözdizimi hatası olduğunu işaret ettim - bu doğru ve ben bir aptalım! Ama bu onu daha da karmaşık hale getiriyor! Kullanmak struct MyObj b = agerçekten işe yarıyor! Burada ne eksik?


24
struct b = a;bir sözdizimi hatasıdır. Ya denersen struct MyObj b = a;?
Greg Hewgill

2
@GregHewgill: Kesinlikle haklısın. Oldukça ilginç, ancak, struct MyObj b = a;işe yarıyor gibi görünüyor :)
mmirzadeh

Yanıtlar:


200

Bir yapıyı bir işlevden =sorunsuz bir şekilde döndürebilirsiniz (veya operatörü kullanabilirsiniz ). Dilin iyi tanımlanmış bir parçası. Tek sorun, struct b = atam bir tür vermedi olmasıdır. struct MyObj b = agayet iyi çalışacak. Sen yapıları geçirmek için de fonksiyonları - bir yapı tam olarak aynı olan herhangi bir dahili parametre geçişi, dönüş değerleri ve atama amacıyla türü.

İşte üçünü de yapan - bir yapıyı parametre olarak geçiren, bir yapıdan bir işlev döndüren ve atama ifadelerinde yapıları kullanan basit bir gösteri programı:

#include <stdio.h>

struct a {
   int i;
};

struct a f(struct a x)
{
   struct a r = x;
   return r;
}

int main(void)
{
   struct a x = { 12 };
   struct a y = f(x);
   printf("%d\n", y.i);
   return 0;
}

Bir sonraki örnek hemen hemen aynıdır, ancak intgösteri için yerleşik türü kullanır . İki program, parametre geçirme, atama vb. İçin by-pass değeri açısından aynı davranışa sahiptir.

#include <stdio.h>

int f(int x) 
{
  int r = x;
  return r;
}

int main(void)
{
  int x = 12;
  int y = f(x);
  printf("%d\n", y);
  return 0;
}

15
Bu oldukça ilginç. Her zaman bunlar için işaretçilere ihtiyaç duyduğunuz izlenimindeydim.
Yanılmışım

8
İşaretçilere kesinlikle ihtiyacınız yok . Bununla birlikte, çoğu zaman bunları kullanmak isteyeceksiniz - flinging yapılarını değere göre yer alan örtük bellek kopyaları, bellek bant genişliğinden bahsetmemeksizin, CPU döngülerinin gerçek bir israfı olabilir.
Carl Norum

10
@CarlNorum Bir yapının malloc + free'den daha pahalıya mal olması için bir yapının büyüklüğü nedir?
josefx

7
@josefx, tek bir kopya mı? Muhtemelen çok büyük. Mesele şu ki, normalde yapıları değerden geçiriyorsanız onları çok fazla kopyalıyorsunuz demektir . Her neyse, bu kadar basit değil. Yerel veya küresel yapılardan geçiyor olabilirsiniz, bu durumda üç tahsis maliyeti hemen hemen ücretsizdir.
Carl Norum

7
Derleme zamanında bir değer için ayrılan bellek miktarı bilinmediğinde, işaretçiler ve işlev gövdesi dışında döndürülen değer için bellek ayırmanız gerekir. Yapılar içindir, bu nedenle C işlevlerinin bunları döndürmede sorun yoktur.
reinierpost

33

Gibi bir çağrı yaparken a = foo();, derleyici yığın üzerinde sonuç yapısının adresini itebilir ve "gizli" bir işaretçi olarak foo()işleve iletebilir . Etkili bir şekilde olabilir:

void foo(MyObj *r) {
    struct MyObj a;
    // ...
    *r = a;
}

foo(&a);

Ancak, bunun tam olarak uygulanması derleyiciye ve / veya platforma bağlıdır. Carl Norum'un belirttiği gibi, yapı yeterince küçükse, bir kayıtta tamamen geri aktarılabilir.


11
Bu tamamen uygulamaya bağlıdır. Örneğin, armcc normal parametre geçiş (veya dönüş değeri) kayıtlarında yeterince küçük yapılar geçirecektir.
Carl Norum

Bu, yerel bir değişkene bir işaretçi döndürmüyor mu? Döndürülen yapının belleği fooyığın çerçevesinin parçası olamaz . Dönüşünden sonra hayatta kalan bir yerde olmalı foo.
Anders Abel

@AndersAbel: Bence Greg'in anlamı derleyicinin ana fonksiyondaki değişkene bir işaretçi alması ve fonksiyona aktarmasıdır foo. Fonksiyonun içinde foo, sadece atamayı yaparsınız
mmirzadeh

4
@AndersAbel: *r = aSonunda yerel değişkenin arayanın değişkenine bir kopyasını (etkin bir şekilde) yapar. Derleyici RVO'yu uygulayabilir ve yerel değişkeni atamamen ortadan kaldırabilir .
Greg Hewgill

3
Bu doğrudan soruyu cevaplamasa da, birçok insanın google üzerinden buraya düşmesinin nedeni budur c return struct: cdecl'de eaxdeğerin döndürüldüğünü ve genel olarak yapıların içine sığmadığını biliyorlar eax. Aradığım şey buydu.
Ciro Santilli 法轮功 冠状 病 六四 事件 法轮功

14

struct bBir sözdizimi hatası çünkü hat çalışmaz. Türünü içerecek şekilde genişletirseniz, iyi çalışır

struct MyObj b = a;  // Runs fine

Burada C'nin yaptığı, esasen memcpykaynak yapıdan hedefe olan bir şeydir. Bu structdeğerlerin hem atanması hem de döndürülmesi (ve gerçekten de C'deki diğer tüm değerler) için geçerlidir.


+1, aslında, birçok derleyici aslında memcpybu durumda gerçek bir çağrı gönderecektir - en azından yapı makul derecede büyükse.
Carl Norum

Yani, bir veri tipinin başlatılması sırasında, memcpy işlevi çalışır ??
bhuwansahni

1
@bhuwansahni Burada ne istediğini tam olarak bilmiyorum. Biraz ayrıntı verebilir misiniz?
JaredPar

4
@JaredPar - derleyiciler sıklıkta çağrı anlamıylamemcpy yapısı durumlar için işlevini. Hızlı bir test programı yapabilir ve GCC'nin bunu yaptığını görebilirsiniz. Gerçekleşmeyecek yerleşik türler için - bu tür bir optimizasyonu tetikleyecek kadar büyük değildir.
Carl Norum

3
Bunun gerçekleşmesi kesinlikle mümkündür - üzerinde çalıştığım projenin memcpytanımlanmış bir sembolü yoktur, bu yüzden derleyici kendi başına tükürmeye karar verdiğinde genellikle "tanımsız sembol" bağlayıcı hatalarıyla karşılaşırız.
Carl Norum

9

evet, yapıyı ve dönüş yapısını da geçebiliriz. Haklıydınız, ama aslında bu yapı gibi olması gereken veri türünü geçmediniz MyObj b = a.

Aslında, işaretçi veya global değişken kullanmadan işlev için birden fazla değer döndürmek için daha iyi bir çözüm bulmaya çalıştığımı da öğrendim .

Şimdi aşağıda, bir öğrencinin ortalama hakkındaki sapmalarını hesaplayan aynı örnek verilmiştir.

#include<stdio.h>
struct marks{
    int maths;
    int physics;
    int chem;
};

struct marks deviation(struct marks student1 , struct marks student2 );

int main(){

    struct marks student;
    student.maths= 87;
    student.chem = 67;
    student.physics=96;

    struct marks avg;
    avg.maths= 55;
    avg.chem = 45;
    avg.physics=34;
    //struct marks dev;
    struct marks dev= deviation(student, avg );
    printf("%d %d %d" ,dev.maths,dev.chem,dev.physics);

    return 0;
 }

struct marks deviation(struct marks student , struct marks student2 ){
    struct marks dev;

    dev.maths = student.maths-student2.maths;
    dev.chem = student.chem-student2.chem;
    dev.physics = student.physics-student2.physics; 

    return dev;
}

5

Hatırlayabildiğim kadarıyla, C'nin ilk sürümleri sadece bir işlemci kaydına sığabilecek bir değer döndürmeye izin verdi, bu da sadece bir yapıya bir işaretçi döndürebileceğiniz anlamına geliyor. İşlev kısıtlamalarına da aynı kısıtlama uygulandı.

Daha yeni sürümler, yapılar gibi daha büyük veri nesnelerinin etrafından geçmesine izin verir. Bence bu özellik seksenli yıllarda veya doksanlı yılların başlarında yaygındı.

Ancak, diziler yine de yalnızca işaretçi olarak geçirilip geri döndürülebilir.


Bir yapının içine koyarsanız, bir diziyi değere göre döndürebilirsiniz. Değere göre döndüremeyeceğiniz bir değişken uzunluklu dizidir.
han

1
Evet, bir yapı içine bir dizi koyabilirsiniz, ancak örneğin typedef char arr [100]; arr foo () {...} Boyut bilinse bile bir dizi döndürülemez.
Giorgio

Downvoter, downvote'un nedenini açıklayabilir mi? Cevabım yanlış bilgi içeriyorsa düzeltmekten memnuniyet duyarım.
Giorgio

4

Sen edebilirsiniz C. içinde yapılar atamak a = b; geçerli sözdizimi.

Satırınızda çalışmayan türün - yapı etiketi - bir kısmını bıraktınız.


4

Bir yapıyı geri aktarmada herhangi bir sorun yoktur. Değere göre geçilecek

Ancak, yapı yerel bir değişkenin adresi olan herhangi bir üye içeriyorsa

struct emp {
    int id;
    char *name;
};

struct emp get() {
    char *name = "John";

    struct emp e1 = {100, name};

    return (e1);
}

int main() {

    struct emp e2 = get();

    printf("%s\n", e2.name);
}

Şimdi, burada e1.name get () fonksiyonuna yerel bir hafıza adresi içermektedir. Get () döndüğünde, adın yerel adresi serbest bırakılır. Bu nedenle, arayanda bu adrese erişmeye çalışırsak, serbest bir adresi denediğimiz için bölümleme hatasına neden olabilir. Bu kötü ..

E1.id değeri mükemmel şekilde geçerli olacağından değeri e2.id olarak kopyalanacaktır

Bu nedenle, her zaman bir işlevin yerel bellek adreslerini döndürmekten kaçınmalıyız.

Bozulan her şey istendiği gibi iade edilebilir


2
struct emp {
    int id;
    char *name;
};

struct emp get() {
    char *name = "John";

    struct emp e1 = {100, name};

    return (e1);
}

int main() {

    struct emp e2 = get();

    printf("%s\n", e2.name);
}

derleyicilerin daha yeni sürümleriyle uyumludur. Tıpkı id gibi, adın içeriği atanan yapı değişkenine kopyalanır.


1
Daha da basit: struct emp get () {return {100, "john"}; }
Chris Reid

1

struct var e2 adresi yığını çağırmak için arg olarak itilir ve değerler orada atanır. Aslında, get () e2'nin adresini eax reg olarak döndürür. Bu, başvuru yoluyla çağrı gibi çalışır.

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.