जवाबों:
आप ऐरे तत्वों को एक विभेदित संघ, उर्फ टैग किया हुआ संघ बना सकते हैं ।
struct {
enum { is_int, is_float, is_char } type;
union {
int ival;
float fval;
char cval;
} val;
} my_array[10];
type
सदस्य पसंद जिनमें से के सदस्य धारण करने के लिए प्रयोग किया जाता है union
प्रत्येक सरणी तत्व के लिए इस्तेमाल किया जाना चाहिए है। इसलिए यदि आप int
पहले तत्व को स्टोर करना चाहते हैं , तो आप ऐसा करेंगे:
my_array[0].type = is_int;
my_array[0].val.ival = 3;
जब आप एरे के किसी तत्व को एक्सेस करना चाहते हैं, तो आपको पहले प्रकार की जांच करनी चाहिए, फिर यूनियन के संबंधित सदस्य का उपयोग करें। एक switch
बयान उपयोगी है:
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
}
यह सुनिश्चित करने के लिए प्रोग्रामर पर छोड़ दिया जाता है कि type
सदस्य हमेशा में संग्रहीत अंतिम मान से मेल खाता है union
।
एक संघ का उपयोग करें:
union {
int ival;
float fval;
void *pval;
} array[10];
आपको प्रत्येक तत्व के प्रकार का ट्रैक रखना होगा, हालाँकि।
सरणी तत्वों का आकार समान होना चाहिए, यही कारण है कि यह संभव नहीं है। आप इसके चारों ओर एक प्रकार का प्रकार बनाकर काम कर सकते हैं :
#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;
}
संघ के तत्व का आकार सबसे बड़े तत्व का आकार, 4 है।
टैग-यूनियन को परिभाषित करने की एक अलग शैली है (जो भी नाम से) कि आंतरिक संघ को हटाकर आईएमओ इसे उपयोग करने के लिए बहुत अच्छा बना देता है । यह इवेंट्स जैसी चीजों के लिए X विंडो सिस्टम में उपयोग की जाने वाली शैली है।
बामार के जवाब में उदाहरण val
आंतरिक संघ को नाम देता है । Sp. के जवाब में उदाहरण एक अनाम यूनियन का उपयोग करता है जिससे .val.
आप हर बार वेरिएंट रिकॉर्ड तक पहुंचने से बचने के लिए निर्दिष्ट कर सकते हैं। दुर्भाग्य से "अनाम" आंतरिक संरचनाएं और यूनियनें C89 या C99 में उपलब्ध नहीं हैं। यह एक संकलक विस्तार है, और इसलिए स्वाभाविक रूप से गैर-पोर्टेबल है।
एक बेहतर तरीका यह है कि IMO पूरी परिभाषा को उलट दे। प्रत्येक डेटा को अपनी स्वयं की संरचना बनाएं, और प्रत्येक संरचना में टैग (प्रकार निर्दिष्ट करें) डालें।
typedef struct {
int tag;
int val;
} integer;
typedef struct {
int tag;
float val;
} real;
फिर आप इन्हें एक शीर्ष-स्तरीय संघ में लपेटते हैं।
typedef union {
int tag;
integer int_;
real real_;
} record;
enum types { INVALID, INT, REAL };
अब यह प्रकट हो सकता है कि हम खुद को दोहरा रहे हैं, और हम हैं । लेकिन विचार करें कि इस परिभाषा को एक फ़ाइल में अलग-थलग करने की संभावना है। लेकिन हमने .val.
डेटा प्राप्त करने से पहले मध्यवर्ती को निर्दिष्ट करने के शोर को समाप्त कर दिया है ।
record i;
i.tag = INT;
i.int_.val = 12;
record r;
r.tag = REAL;
r.real_.val = 57.0;
इसके बजाय, यह अंत में जाता है, जहां यह कम अप्रिय है। : डी
एक और बात यह अनुमति देता है विरासत का एक रूप है। संपादित करें: यह भाग मानक C नहीं है, लेकिन GNU एक्सटेंशन का उपयोग करता है।
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;
अप-कास्टिंग और डाउन-कास्टिंग।
संपादित करें: यदि आपको C99 नामित इनिशियलाइज़र के साथ इनमें से किसी एक का निर्माण हो रहा है, तो इसके बारे में पता होना चाहिए। सभी सदस्य आरंभकर्ता एक ही संघ के सदस्य के माध्यम से होने चाहिए।
record problem = { .tag = INT, .int_.val = 3 };
problem.tag; // may not be initialized
एक .tag
प्रारंभिक संकलक द्वारा इनिशियलाइज़र को अनदेखा किया जा सकता है, क्योंकि एलिज़र जो एक ही डेटा क्षेत्र का .int_
अनुसरण करता है। भले ही हम लेआउट (!) को जानते हैं, और यह ठीक होना चाहिए। नहीं, यह नहीं है। इसके बजाय "आंतरिक" टैग का उपयोग करें (यह बाहरी टैग को ओवरले करता है, जैसे हम चाहते हैं, लेकिन संकलक को भ्रमित नहीं करता है)।
record not_a_problem = { .int_.tag = INT, .int_.val = 3 };
not_a_problem.tag; // == INT
.int_.val
उसी क्षेत्र को उर्फ नहीं करता है क्योंकि संकलक जानता है कि .val
तुलना में अधिक ऑफसेट पर है .tag
। क्या आपको इस कथित समस्या के बारे में आगे चर्चा करने के लिए एक लिंक मिला है?
आप एक void *
सरणी कर सकते हैं , size_t.
लेकिन एक अलग सरणी के साथ आप सूचना प्रकार खो देते हैं।
यदि आपको किसी प्रकार से सूचना प्रकार रखने की आवश्यकता है, तो तीसरे चरण का int रखें (जहाँ int एक एनुमरेटेड वैल्यू है) तब मान के आधार पर कार्य करने वाले फ़ंक्शन को कोड करें enum
।
संघ जाने का मानक तरीका है। लेकिन आपके पास अन्य उपाय भी हैं। उनमें से एक को पॉइंटर टैग किया गया है , जिसमें पॉइंटर के "फ्री" बिट्स में अधिक जानकारी संग्रहीत करना शामिल है ।
आर्किटेक्चर के आधार पर आप निम्न या उच्च बिट्स का उपयोग कर सकते हैं, लेकिन सबसे सुरक्षित और सबसे पोर्टेबल तरीका संरेखित मेमोरी का लाभ उठाकर अप्रयुक्त कम बिट्स का उपयोग कर रहा है। उदाहरण के लिए 32-बिट और 64-बिट सिस्टम में, पॉइंटर्स int
4 के गुणक होने चाहिए (यह मानते हुए int
कि 32-बिट प्रकार है) और 2 कम से कम महत्वपूर्ण बिट्स 0 होना चाहिए, इसलिए आप उन्हें अपने मूल्यों के प्रकार को स्टोर करने के लिए उपयोग कर सकते हैं । बेशक आपको पॉइंटर को डीफ़र करने से पहले टैग बिट्स को साफ़ करना होगा। उदाहरण के लिए यदि आपका डेटा प्रकार 4 विभिन्न प्रकारों तक सीमित है तो आप इसे नीचे की तरह उपयोग कर सकते हैं
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;
}
आप सुनिश्चित कर सकते हैं डेटा 8-बाइट गठबंधन है कि (जैसे 64-बिट सिस्टम, या में संकेत के लिए long long
और uint64_t
...), आप टैग के लिए एक और बिट होगा।
इसका एक नुकसान यह है कि अगर आपको डेटा कहीं और वैरिएबल में स्टोर नहीं किया गया है तो आपको अधिक मेमोरी की आवश्यकता होगी। इसलिए यदि आपके डेटा का प्रकार और सीमा सीमित है, तो आप सीधे पॉइंटर में मान स्टोर कर सकते हैं। क्रोम के V8 इंजन के 32-बिट संस्करण में इस तकनीक का उपयोग किया गया है , जहां यह पता लगाने के लिए कम से कम महत्वपूर्ण बिट की जांच करता है कि क्या यह किसी अन्य ऑब्जेक्ट (जैसे डबल, बड़े पूर्णांक, स्ट्रिंग या कुछ ऑब्जेक्ट) का सूचक है या 31 -बिट हस्ताक्षरित मूल्य (कहा जाता है smi
- छोटे पूर्णांक )। यदि यह एक है int
, तो क्रोम मूल्य प्राप्त करने के लिए केवल एक अंकगणितीय दायाँ शिफ्ट करता है, अन्यथा सूचक को निष्क्रिय कर दिया जाता है।
अधिकांश वर्तमान 64-बिट सिस्टम पर वर्चुअल पता स्थान अभी भी 64 बिट्स की तुलना में बहुत संकरा है, इसलिए उच्च सबसे महत्वपूर्ण बिट्स को टैग के रूप में भी उपयोग किया जा सकता है । आर्किटेक्चर के आधार पर आपके पास टैग के रूप में उपयोग करने के विभिन्न तरीके हैं। एआरएम , 68k और कई अन्य को शीर्ष बिट्स को अनदेखा करने के लिए कॉन्फ़िगर किया जा सकता है , जिससे आप उन्हें सेगफॉल्ट या किसी भी चीज की चिंता किए बिना स्वतंत्र रूप से उपयोग करने की अनुमति दे सकते हैं। ऊपर दिए गए लिंक विकिपीडिया लेख से:
टैग किए गए पॉइंटर्स के उपयोग का एक महत्वपूर्ण उदाहरण ARM64 पर iOS 7 पर ऑब्जेक्टिव-सी रनटाइम है, विशेष रूप से iPhone 5S पर उपयोग किया जाता है। IOS 7 में, वर्चुअल एड्रेस 33 बिट्स (बाइट-एलायड) हैं, इसलिए वर्ड-एलायंस एड्रेस केवल 30 बिट्स (3 कम से कम महत्वपूर्ण बिट्स 0) का उपयोग करते हैं, टैग के लिए 34 बिट्स को छोड़ते हैं। ऑब्जेक्टिव-सी क्लास पॉइंटर्स शब्द-संरेखित होते हैं, और टैग फ़ील्ड का उपयोग कई उद्देश्यों के लिए किया जाता है, जैसे कि एक संदर्भ गणना को संग्रहीत करना और क्या ऑब्जेक्ट का विध्वंसक है।
MacOS के शुरुआती संस्करणों में डेटा ऑब्जेक्ट्स के संदर्भों को संग्रहीत करने के लिए हैंडल नामक टैग्स का उपयोग किया गया था। पते के उच्च बिट्स ने संकेत दिया कि क्या डेटा ऑब्जेक्ट क्रमशः संसाधन फ़ाइल से लॉक, प्योरिफ़ायर और / या उत्पन्न हुआ था। सिस्टम 7 में मैकस 24 बिट्स से 32 बिट्स तक एडवांस होने पर यह संगतता समस्याओं का कारण बना।
X86_64 पर आप अभी भी देखभाल के साथ टैग के रूप में उच्च बिट्स का उपयोग कर सकते हैं । बेशक आपको उन सभी 16 बिट्स का उपयोग करने की आवश्यकता नहीं है और भविष्य के सबूत के लिए कुछ बिट्स को छोड़ सकते हैं
मोज़िला फ़ायरफ़ॉक्स के पूर्व संस्करणों में वे V8 की तरह छोटे पूर्णांक अनुकूलन का भी उपयोग करते हैं , जिसमें 3 निम्न बिट्स प्रकार (इंट, स्ट्रिंग, ऑब्जेक्ट ... आदि) को स्टोर करने के लिए उपयोग किए जाते हैं । लेकिन JägerMonkey के बाद से उन्होंने एक और रास्ता ( मोज़िला का न्यू जावास्क्रिप्ट वैल्यू रिप्रेजेंटेशन , बैकअप लिंक ) ले लिया। मान अब हमेशा 64-बिट डबल सटीक चर में संग्रहीत किया जाता है। जब double
एक सामान्यीकृत होता है, तो इसका उपयोग सीधे गणना में किया जा सकता है। हालाँकि यदि इसके उच्च 16 बिट्स सभी 1s हैं, जो एक NaN को दर्शाते हैं , कम 32-बिट्स पते (एक 32-बिट कंप्यूटर में) को सीधे मान या मान में संग्रहीत करेंगे, शेष 16-बिट का उपयोग किया जाएगा प्रकार स्टोर करने के लिए। इस तकनीक को NaN- बॉक्सिंग कहा जाता हैया नन-बॉक्सिंग। इसका उपयोग 64-बिट WebKit के JavaScriptCore और Mozilla के SpiderMonkey में भी किया जाता है, जिसमें सूचक कम 48 बिट्स में संग्रहीत किया जाता है। यदि आपका मुख्य डेटा प्रकार फ़्लोटिंग-पॉइंट है, तो यह सबसे अच्छा समाधान है और बहुत अच्छा प्रदर्शन देता है।
उपरोक्त तकनीकों के बारे में और पढ़ें: https://wingolog.org/archives/2011/05/18/value-repretation-in-javascript-implementations