Seberapa berguna ukuran variabel C yang sebenarnya?

9

Satu hal yang secara intuitif selalu mengejutkan saya sebagai fitur positif C (well, sebenarnya implementasinya seperti gcc, clang, ...) adalah kenyataan bahwa ia tidak menyimpan informasi tersembunyi apa pun di sebelah variabel Anda sendiri saat runtime. Maksud saya, jika Anda misalnya menginginkan variabel "x" dari jenis "uint16_t", Anda dapat yakin bahwa "x" hanya akan menempati 2 byte ruang (dan tidak akan membawa informasi tersembunyi seperti jenisnya dll. .). Demikian pula, jika Anda menginginkan array 100 bilangan bulat, Anda bisa yakin itu sebesar 100 bilangan bulat.

Namun, semakin saya mencoba untuk datang dengan kasus penggunaan beton untuk fitur ini semakin saya bertanya-tanya apakah itu benar-benar memiliki setiap keuntungan praktis sama sekali. Satu-satunya hal yang saya dapat sampai sejauh ini adalah bahwa itu jelas membutuhkan lebih sedikit RAM. Untuk lingkungan terbatas, seperti chip AVR dll., Ini jelas merupakan nilai tambah yang besar, tetapi untuk kasus penggunaan desktop / server sehari-hari, tampaknya agak tidak relevan. Kemungkinan lain yang saya pikirkan adalah mungkin bermanfaat / penting untuk mengakses perangkat keras, atau mungkin memetakan wilayah memori (misalnya untuk output VGA dan sejenisnya) ...?

Pertanyaan saya: Apakah ada domain konkret yang tidak dapat atau hanya dapat diimplementasikan dengan sangat rumit tanpa fitur ini?

PS Tolong beritahu saya jika Anda memiliki nama yang lebih baik untuk itu! ;)

Thomas Oltmann
sumber
@gnat Saya pikir saya mengerti apa masalah Anda. Itu karena mungkin ada beberapa jawaban, bukan? Yah, saya mengerti bahwa pertanyaan ini mungkin tidak sesuai dengan cara kerja stackexchange, tapi jujur ​​saya tidak tahu harus bertanya ke mana ...
Thomas Oltmann
1
@ lxrec RTTI disimpan di vtable, dan objek hanya menyimpan pointer ke vtable. Selain itu, tipe hanya memiliki RTTI jika sudah memiliki vtable karena mereka memiliki virtualfungsi anggota. Jadi RTTI tidak pernah meningkatkan ukuran benda apa pun, itu hanya membuat biner lebih besar dengan konstanta.
3
@ThomasOltmann Setiap objek yang memiliki metode virtual memerlukan pointer vtable. Anda tidak dapat memiliki metode virtual fungsionalitas tanpa itu. Selain itu, Anda secara eksplisit memilih memiliki metode virtual (dan karenanya, vtable).
1
@ThomasOltmann Anda tampak sangat bingung. Ini bukan pointer ke objek yang membawa pointer vtable, itu objek itu sendiri. Yaitu, T *selalu berukuran sama dan Tmungkin berisi bidang tersembunyi yang menunjuk ke vtable. Dan tidak ada kompiler C ++ yang pernah memasukkan vtables ke objek yang tidak membutuhkannya.

Jawaban:

5

Ada beberapa manfaat, yang jelas adalah pada waktu kompilasi untuk memastikan bahwa hal-hal seperti parameter fungsi cocok dengan nilai yang diteruskan.

Tapi saya pikir Anda bertanya tentang apa yang terjadi pada saat runtime.

Perlu diingat bahwa kompiler akan membuat runtime yang menanamkan pengetahuan tentang tipe data dalam operasi yang dilakukannya. Setiap potongan data dalam memori mungkin tidak menggambarkan diri, tetapi kode secara inheren tahu apa data itu (jika Anda telah melakukan pekerjaan Anda dengan benar).

Saat runtime hal-hal sedikit berbeda dari yang Anda pikirkan.

Misalnya, jangan berasumsi bahwa hanya dua byte yang digunakan ketika Anda mendeklarasikan uint16_t. Tergantung pada prosesor dan penyelarasan kata itu dapat menempati 16, 32 atau 64 bit pada tumpukan. Anda mungkin menemukan bahwa array celana pendek Anda mengkonsumsi lebih banyak memori daripada yang Anda harapkan.

Ini bisa menjadi masalah dalam situasi tertentu di mana Anda perlu referensi data pada offset tertentu. Ini terjadi ketika berkomunikasi antara dua sistem yang memiliki arsitektur prosesor yang berbeda, baik melalui tautan nirkabel, atau melalui file.

C memungkinkan Anda untuk menentukan struct dengan granularity level bit:

struct myMessage {
  uint8_t   first_bit: 1;
  uint8_t   second_bit: 1;
  uint8_t   padding:6;
  uint16_t  somethingUseful;
}

Struktur ini panjangnya tiga byte, dengan pendek didefinisikan untuk memulai pada offset aneh. Ini juga perlu dikemas agar persis seperti yang Anda definisikan. Kalau tidak, kompiler akan menyelaraskan kata para anggota.

Kompiler akan menghasilkan kode di belakang layar untuk mengekstrak data ini dan menyalinnya ke dalam register sehingga Anda dapat melakukan hal-hal yang bermanfaat dengannya.

Sekarang Anda dapat melihat bahwa setiap kali program saya mengakses anggota dari myMessage struct, ia akan tahu bagaimana tepatnya mengekstraksi dan mengoperasikannya.

Ini bisa menjadi masalah dan sulit untuk dikelola ketika berkomunikasi antara sistem yang berbeda dengan versi perangkat lunak yang berbeda. Anda harus hati-hati merancang sistem & kode untuk memastikan kedua belah pihak memiliki definisi yang sama persis dari tipe data. Ini bisa sangat menantang di beberapa lingkungan. Di sinilah Anda memerlukan protokol yang lebih baik yang berisi data uraian sendiri seperti Google's Protocol Buffer .

Terakhir, Anda membuat poin yang bagus untuk menanyakan seberapa penting hal ini di lingkungan desktop / server. Ini benar-benar tergantung pada berapa banyak memori yang Anda rencanakan untuk digunakan. Jika Anda melakukan sesuatu seperti pemrosesan gambar, Anda mungkin akan menggunakan memori dalam jumlah besar yang dapat mempengaruhi kinerja aplikasi Anda. Ini pasti selalu menjadi perhatian di lingkungan tertanam di mana memori dibatasi dan tidak ada memori virtual.

Tereus Scott
sumber
2
"Anda mungkin menemukan bahwa array celana pendek Anda mengkonsumsi lebih banyak memori daripada yang Anda harapkan." Ini salah dalam C: Array dijamin mengandung elemen-elemen mereka secara bebas celah. Ya, array perlu disejajarkan dengan benar, seperti halnya satu short. Tetapi ini adalah persyaratan satu kali untuk memulai array, sisanya secara otomatis disejajarkan berdasarkan berturut-turut.
cmaster - mengembalikan monica
Juga, sintaks untuk padding salah, seharusnya uint8_t padding: 6;, seperti dua bit pertama. Atau, lebih jelas, hanya komentar //6 bits of padding inserted by the compiler. Struktur, seperti yang Anda tulis, memiliki ukuran setidaknya sembilan byte, bukan tiga.
cmaster - mengembalikan monica
9

Anda menekan satu-satunya alasan ini berguna: memetakan struktur data eksternal. Itu termasuk buffer video yang dipetakan memori, register perangkat keras, dll. Mereka juga memasukkan data yang ditransmisikan secara utuh di luar program, seperti sertifikat SSL, paket IP, gambar JPEG, dan hampir semua struktur data lain yang memiliki umur yang bertahan di luar program.

Ross Patterson
sumber
5

C adalah bahasa tingkat rendah, hampir merupakan perakit portabel, sehingga struktur data dan konstruk bahasanya mendekati logam (struktur data tidak memiliki biaya tersembunyi - kecuali batasan bantalan, pelurusan dan ukuran yang dikenakan oleh perangkat keras dan ABI ). Jadi C memang tidak memiliki pengetikan dinamis secara asli. Tetapi jika Anda membutuhkannya, Anda dapat mengadopsi konvensi bahwa semua nilai Anda adalah agregat yang dimulai dengan beberapa jenis informasi (misalnya beberapa enum...); use union-s dan (untuk array-like things) anggota array fleksibel dalam structmemuat juga ukuran array.

(saat memprogram dalam C, Anda bertanggung jawab untuk mendefinisikan, mendokumentasikan, dan mengikuti konvensi yang bermanfaat - terutama pra dan pasca kondisi dan invarian; juga alokasi memori dinamis C memerlukan konvensi penjelasan tentang siapa yang harus memiliki freebeberapa malloczona memori yang ditumpuk )

Jadi, untuk merepresentasikan nilai-nilai yang merupakan bilangan bulat kotak, atau string, atau semacam simbol seperti Skema , atau vektor nilai, Anda akan secara konseptual menggunakan gabungan tag (diimplementasikan sebagai gabungan pointer) -selalu dimulai dengan jenis ketik -, misalnya:

enum value_kind_en {V_NONE, V_INT, V_STRING, V_SYMBOL, V_VECTOR};
union value_en { // this union takes a word in memory
   const void* vptr; // generic pointer, e.g. to free it
   enum value_kind_en* vkind; // the value of *vkind decides which member to use
   struct intvalue_st* vint;
   struct strvalue_st* vstr;
   struct symbvalue_st* vsymb;
   struct vectvalue_st* vvect;
};
typedef union value_en value_t;
#define NULL_VALUE  ((value_t){NULL})
struct intvalue_st {
  enum value_kind_en kind; // always V_INT for intvalue_st
  int num;
};
struct strvalue_st {
  enum value_kind_en kind; // always V_STRING for strvalue_st
  const char*str;
};
struct symbvalue_st {
  enum value_kind_en kind; // V_SYMBOL
  struct strvalue_st* symbname;
  value_t symbvalue;
};
struct vectvalue_st {
  enum value_kind_en kind; // V_VECTOR;
  unsigned veclength;
  value_t veccomp[]; // flexible array of veclength components.
};

Untuk mendapatkan tipe nilai yang dinamis

enum value_kind_en value_type(value_t v) {
  if (v.vptr != NULL) return *(v.vkind);
  else return V_NONE;
}

Berikut ini adalah "pemeran dinamis" untuk vektor:

struct vectvalue_st* dyncast_vector (value_t v) {
   if (value_type(v) == V_VECTOR) return v->vvect;
   else return NULL;
}

dan vektor "safe accessor" di dalam:

value_t vector_nth(value_t v, unsigned rk) {
   struct vectvalue_st* vecp = dyncast_vector(v);
   if (vecp && rk < vecp->veclength) return vecp->veccomp[rk];
   else return NULL_VALUE;
}

Anda biasanya akan mendefinisikan sebagian besar fungsi pendek di atas seperti static inlinepada beberapa file header.

BTW, jika Anda dapat menggunakan pengumpul sampah Boehm, Anda kemudian dapat membuat kode dengan mudah dalam gaya tingkat tinggi (tetapi tidak aman), dan beberapa juru bahasa Skema melakukannya dengan cara itu. Konstruktor vektor variadik bisa jadi

value_t make_vector(unsigned size, ... /*value_t arguments*/) {
   struct vectvalue_st* vec = GC_MALLOC(sizeof(*vec)+size*sizeof(value));
   vec->kind = V_VECTOR;
   va_args args;
   va_start (args, size);
   for (unsigned ix=0; ix<size; ix++) 
     vec->veccomp[ix] = va_arg(args,value_t);
   va_end (args);
   return (value_t){vec};
}

dan jika Anda memiliki tiga variabel

value_t v1 = somevalue(), v2 = otherval(), v3 = NULL_VALUE;

Anda dapat membangun vektor dari mereka menggunakan make_vector(3,v1,v2,v3)

Jika Anda tidak ingin menggunakan pengumpul sampah Boehm (atau mendesain sendiri milik Anda), Anda harus sangat berhati-hati dalam mendefinisikan destruktor dan mendokumentasikan siapa, bagaimana, dan kapan memori harus free-d; lihat contoh ini . Jadi Anda bisa menggunakan malloc(tapi kemudian menguji kegagalannya) alih-alih di GC_MALLOCatas tetapi Anda perlu mendefinisikan dan menggunakan beberapa fungsi destruktor dengan hati-hativoid destroy_value(value_t)

Kekuatan C adalah level yang cukup rendah untuk membuat kode seperti di atas menjadi mungkin dan mendefinisikan konvensi Anda sendiri (khusus untuk perangkat lunak Anda).

Basile Starynkevitch
sumber
Saya pikir Anda salah mengerti pertanyaan saya. Saya tidak ingin pengetikan dinamis dalam C. Saya ingin tahu apakah sifat khusus C ini dapat digunakan secara praktis.
Thomas Oltmann
Tapi properti C apa yang Anda maksud? Struktur data C dekat dengan logam, sehingga tidak memiliki biaya tersembunyi (kecuali kendala penyelarasan dan ukuran)
Basile Starynkevitch
Persis seperti itu: /
Thomas Oltmann
C diciptakan sebagai bahasa tingkat rendah, tetapi ketika optimisasi dihidupkan kompiler seperti proses gcc bahasa yang menggunakan sintaks tingkat rendah tetapi tidak andal menyediakan akses tingkat rendah ke jaminan perilaku yang disediakan platform. Kita membutuhkan ukuran untuk menggunakan malloc dan memcpy, tetapi penggunaan untuk perhitungan alamat yang lebih menarik mungkin tidak didukung dalam C. "modern"
supercat