Karma veri türleri (int, float, char, vb.) Bir dizide nasıl saklanabilir?


146

Karma veri türlerini bir dizide depolamak istiyorum. Bunu nasıl yapabilirsin?


9
Mümkün ve kullanım durumları var, ancak bu muhtemelen kusurlu bir tasarım. Diziler bunun için değil.
djechlin

Yanıtlar:


245

Dizi öğelerini ayırt edici bir birleşim, yani etiketli birleşim yapabilirsiniz .

struct {
    enum { is_int, is_float, is_char } type;
    union {
        int ival;
        float fval;
        char cval;
    } val;
} my_array[10];

typeÜyesi elemanı, seçim tutmak için kullanılan unionher bir dizi elemanı için kullanılmalıdır edilir. Dolayısıyla int, ilk öğede bir depolamak istiyorsanız , şunları yaparsınız:

my_array[0].type = is_int;
my_array[0].val.ival = 3;

Dizinin bir öğesine erişmek istediğinizde, önce türü kontrol etmeli, ardından birleşimin ilgili üyesini kullanmalısınız. Bir switchifade yararlıdır:

switch (my_array[n].type) {
case is_int:
    // Do stuff for integer, using my_array[n].ival
    break;
case is_float:
    // Do stuff for float, using my_array[n].fval
    break;
case is_char:
    // Do stuff for char, using my_array[n].cvar
    break;
default:
    // Report an error, this shouldn't happen
}

Üyenin typeher zaman içinde depolanan son değere karşılık geldiğinden emin olmak programcıya bırakılmıştır union.


23
+1 Bu, C
texasbruce

8
@texasbruce ayrıca "etiketli birleşme" olarak da adlandırılır. Bu tekniği de kendi dilimde kullanıyorum. ;)

Wikipedia, küme teorisinde " ayrımcılığa dayalı birleşim " - "ayrık birleşim" ve @ H2CO3'ün de belirtildiği gibi, bilgisayar biliminde "etiketli birleşim" için bir belirsizlik giderme sayfası kullanır .
Izkata

14
Ve Wikipedia Etiketli sendika sayfasının ilk satırı şöyle diyor: Bilgisayar biliminde, değişken, değişken kaydı, ayrımcılığa uğramış birleşme, ayrık birleşme veya toplam türü olarak da adlandırılan etiketli bir sendika ... O kadar çok yeniden keşfedildi ki, isimler (sözlükler, karmalar, ilişkilendirilebilir diziler vb. gibi).
Barmar

1
@Barmar "tagged union" olarak yeniden yazdım ama sonra yorumunuzu okudum. Düzenlemeyi geri aldım, cevabınızı bozmak istemedim.

33

Bir sendika kullanın:

union {
    int ival;
    float fval;
    void *pval;
} array[10];

Yine de, her bir öğenin türünü izlemeniz gerekecek.


21

Dizi öğelerinin aynı boyutta olması gerekir, bu yüzden mümkün değildir. Bir varyant türü oluşturarak bunun üstesinden gelebilirsiniz :

#include <stdio.h>
#define SIZE 3

typedef enum __VarType {
  V_INT,
  V_CHAR,
  V_FLOAT,
} VarType;

typedef struct __Var {
  VarType type;
  union {
    int i;
    char c;
    float f;
  };
} Var;

void var_init_int(Var *v, int i) {
  v->type = V_INT;
  v->i = i;
}

void var_init_char(Var *v, char c) {
  v->type = V_CHAR;
  v->c = c;
}

void var_init_float(Var *v, float f) {
  v->type = V_FLOAT;
  v->f = f;
}

int main(int argc, char **argv) {

  Var v[SIZE];
  int i;

  var_init_int(&v[0], 10);
  var_init_char(&v[1], 'C');
  var_init_float(&v[2], 3.14);

  for( i = 0 ; i < SIZE ; i++ ) {
    switch( v[i].type ) {
      case V_INT  : printf("INT   %d\n", v[i].i); break;
      case V_CHAR : printf("CHAR  %c\n", v[i].c); break;
      case V_FLOAT: printf("FLOAT %f\n", v[i].f); break;
    }
  }

  return 0;
}

Birliğin elemanının boyutu, en büyük elemanın boyutudur, 4.


8

IMO'nun dahili birleşimi kaldırarak kullanımını daha hoş hale getirdiği etiket birleşimini (adı ne olursa olsun) tanımlamanın farklı bir tarzı vardır . Bu, Etkinlikler gibi şeyler için X Pencere Sisteminde kullanılan stildir.

Barmar'ın cevabındaki örnek val, iç birliğe adını verir . Sp.'nin cevabındaki örnek .val., varyant kaydına her eriştiğinizde belirtmek zorunda kalmamak için anonim bir birleşim kullanır . Maalesef "anonim" dahili yapılar ve birlikler C89 veya C99'da mevcut değildir. Bu bir derleyici uzantısıdır ve bu nedenle doğası gereği taşınabilir değildir.

IMO'nun daha iyi bir yolu, tüm tanımı tersine çevirmektir. Her veri türünü kendi yapısı haline getirin ve etiketi (tür belirleyici) her yapıya yerleştirin.

typedef struct {
    int tag;
    int val;
} integer;

typedef struct {
    int tag;
    float val;
} real;

Sonra bunları üst düzey bir birleşimle sarmalıyorsunuz.

typedef union {
    int tag;
    integer int_;
    real real_;
} record;

enum types { INVALID, INT, REAL };

Şimdi kendimizi tekrar ediyoruz görünebilir ve biz vardır . Ancak bu tanımın muhtemelen tek bir dosyaya izole edileceğini düşünün. Ancak, .val.siz verilere ulaşmadan önce ara ürünü belirleme gürültüsünü ortadan kaldırdık .

record i;
i.tag = INT;
i.int_.val = 12;

record r;
r.tag = REAL;
r.real_.val = 57.0;

Bunun yerine, daha az iğrenç olduğu en sonunda gider. : D

Bunun izin verdiği başka bir şey de bir tür kalıtımdır. Düzenleme: bu bölüm standart C değildir, ancak bir GNU uzantısı kullanır.

if (r.tag == INT) {
    integer x = r;
    x.val = 36;
} else if (r.tag == REAL) {
    real x = r;
    x.val = 25.0;
}

integer g = { INT, 100 };
record rg = g;

Yukarı ve aşağı döküm.


Düzenleme: Dikkat edilmesi gereken bir şey, bunlardan birini C99 tarafından belirlenmiş başlatıcılarla oluşturuyor olmanızdır. Tüm üye başlatıcılar aynı sendika üyesi aracılığıyla olmalıdır.

record problem = { .tag = INT, .int_.val = 3 };

problem.tag; // may not be initialized

.tagBaşlatıcı, bir optimize derleyici tarafından göz ardı edilebilir, çünkü .int_şu başlatıcısı diğer adları aynı veri alanının. Olsa biz düzen (!) Biliyorum ve bu gerektiğini olmak ok. Hayır, değil. Bunun yerine "dahili" etiketi kullanın (tıpkı istediğimiz gibi dış etiketi kaplar, ancak derleyicinin kafasını karıştırmaz).

record not_a_problem = { .int_.tag = INT, .int_.val = 3 };

not_a_problem.tag; // == INT

.int_.valderleyici .valdaha büyük bir ofset olduğunu bildiği için aynı alanı takma ad değildir .tag. Bu iddia edilen sorun hakkında daha fazla tartışma için bir bağlantınız var mı?
MM

5

void *Ayrı bir dizi ile bir dizi yapabilirsiniz size_t.Ama bilgi türünü kaybedersiniz.
Bilgi türünü bir şekilde tutmanız gerekiyorsa, üçüncü bir int dizisi tutun (burada int numaralandırılmış bir değerdir) Sonra değere bağlı olarak döndüren işlevi kodlayın enum.



3

Birlik, gitmenin standart yoludur. Ancak başka çözümleriniz de var. Bunlardan biri, bir göstericinin "serbest" bitlerinde daha fazla bilgi depolamayı içeren etiketli işaretçi.

Mimarilere bağlı olarak düşük veya yüksek bitleri kullanabilirsiniz, ancak en güvenli ve en taşınabilir yol, hizalanmış bellekten yararlanarak kullanılmayan düşük bitleri kullanmaktır . Örneğin 32 bit ve 64 bit sistemlerde işaretçiler int4'ün katları olmalıdır ( int32 bitlik bir tür olduğu varsayılırsa ) ve en az önemli 2 bit 0 olmalıdır, bu nedenle bunları değerlerinizi depolamak için kullanabilirsiniz. . Elbette işaretçiyi referans almadan önce etiket bitlerini temizlemeniz gerekir. Örneğin, veri türünüz 4 farklı türle sınırlıysa, aşağıdaki gibi kullanabilirsiniz

void* tp; // tagged pointer
enum { is_int, is_double, is_char_p, is_char } type;
// ...
uintptr_t addr = (uintptr_t)tp & ~0x03; // clear the 2 low bits in the pointer
switch ((uintptr_t)tp & 0x03)           // check the tag (2 low bits) for the type
{
case is_int:    // data is int
    printf("%d\n", *((int*)addr));
    break;
case is_double: // data is double
    printf("%f\n", *((double*)addr));
    break;
case is_char_p: // data is char*
    printf("%s\n", (char*)addr);
    break;
case is_char:   // data is char
    printf("%c\n", *((char*)addr));
    break;
}

Verinin 8 bayt hizalı olduğundan emin olursanız (64 bit sistemlerdeki işaretçiler için olduğu gibi veya long longve uint64_t...), etiket için bir bit daha olur.

Bunun bir dezavantajı, veriler başka bir yerde bir değişkende saklanmamışsa daha fazla belleğe ihtiyaç duymanızdır. Bu nedenle, verilerinizin türü ve aralığının sınırlı olması durumunda, değerleri doğrudan işaretçide saklayabilirsiniz. Bu teknik, Chrome'un V8 motorunun 32-bit sürümünde kullanılmıştır , burada adresin en önemsiz bitini kontrol ederek bunun başka bir nesneye (çift, büyük tamsayılar, dize veya bir nesne gibi) işaretçi mi yoksa 31 -bit işaretli değer ( smi- küçük tamsayı olarak adlandırılır ). Eğer bir ise int, Chrome değeri elde etmek için aritmetik olarak 1 bitlik bir sağa kaydırma yapar, aksi takdirde imlece başvurulur.


Mevcut 64 bitlik sistemlerin çoğunda, sanal adres alanı hala 64 bitten çok daha dardır, bu nedenle en önemli bitler etiket olarak da kullanılabilir . Mimariye bağlı olarak, bunları etiket olarak kullanmanın farklı yolları vardır. ARM , 68k ve diğerleri, en üst bitleri yok sayacak şekilde yapılandırılabilir ve böylelikle segfault veya herhangi bir şey hakkında endişelenmenize gerek kalmadan bunları özgürce kullanmanızı sağlar. Yukarıdaki bağlantılı Wikipedia makalesinden:

Etiketli işaretçilerin kullanımının önemli bir örneği, özellikle iPhone 5S'de kullanılan, ARM64'te iOS 7'de Objective-C çalışma zamanıdır. İOS 7'de sanal adresler 33 bittir (bayt hizalı), bu nedenle sözcük hizalı adresler yalnızca 30 bit kullanır (en az önemli 3 bit 0'dır) ve etiketler için 34 bit kalır. Objective-C sınıfı işaretçiler kelime hizalıdır ve etiket alanları, bir referans sayısının depolanması ve nesnenin bir yıkıcı olup olmadığı gibi birçok amaç için kullanılır.

MacOS'un ilk sürümleri, veri nesnelerine referansları depolamak için Handles adı verilen etiketli adresleri kullanıyordu. Adresin yüksek bitleri, veri nesnesinin sırasıyla kilitli, temizlenebilir ve / veya bir kaynak dosyasından kaynaklanıp kaynaklanmadığını gösterir. Bu, Sistem 7'de MacOS adresleme 24 bitten 32 bit'e ilerlediğinde uyumluluk sorunlarına neden oldu.

https://en.wikipedia.org/wiki/Tagged_pointer#Examples

X86_64'te yine de yüksek bitleri dikkatli bir şekilde etiket olarak kullanabilirsiniz . Elbette tüm bu 16 biti kullanmanıza gerek yoktur ve ileride kanıtlamak için bazı bitleri dışarıda bırakabilirsiniz.

Mozilla Firefox'un önceki sürümlerinde , türü depolamak için kullanılan 3 düşük bit ile V8 gibi küçük tamsayı optimizasyonları da kullanıyorlardı (int, string, object ... vb.). Ancak JägerMonkey'den beri başka bir yol izlediler ( Mozilla'nın Yeni JavaScript Değer Temsili , yedek bağlantı ). Değer artık her zaman 64 bitlik çift duyarlıklı bir değişkende saklanır. Ne zaman bir olan normalize bir, o hesaplamalarda doğrudan kullanılabilir. Bununla birlikte, yüksek 16 bitinin tümü bir NaN'yi ifade eden 1'ler ise, düşük 32 bit, adresi (32 bitlik bir bilgisayarda) değere veya değere doğrudan saklar, kalan 16 bit kullanılır türü saklamak için. Bu tekniğe NaN-boks denirdoubleya da rahibe boks. Ayrıca, 64-bit WebKit'in JavaScriptCore ve Mozilla'nın SpiderMonkey'inde, işaretçi düşük 48 bitte saklanırken kullanılır. Ana veri türünüz kayan nokta ise, bu en iyi çözümdür ve çok iyi performans sağlar.

Yukarıdaki teknikler hakkında daha fazla bilgi edinin: https://wingolog.org/archives/2011/05/18/value-representation-in-javascript-implementations

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.