Наскільки корисне C "справжнє" розмір змінних?


9

Одна річ, яка завжди інтуїтивно вражала мене позитивною особливістю C (ну, власне, її реалізацій, таких як gcc, clang, ...) - це той факт, що вона не зберігає прихованої інформації поруч із вашими власними змінними під час виконання. Під цим я маю на увазі, що якщо ви, наприклад, хотіли змінної "x" типу "uint16_t", ви можете бути впевнені, що "x" займе лише 2 байти простору (і не буде нести жодної прихованої інформації, наприклад його типу тощо) .). Точно так само, якби ви хотіли масив із 100 цілих чисел, ви можете бути впевнені, що він такий великий, як 100 цілих чисел.

Однак чим більше я намагаюся придумати конкретні випадки використання цієї функції, тим більше мені цікаво, чи має вона насправді якісь практичні переваги. Єдине, до чого я міг придумати, - це, очевидно, що потрібно менше оперативної пам’яті. Для обмежених середовищ, таких як AVR-мікросхеми тощо, це, безумовно, величезний плюс, але для повсякденних випадків використання настільних ПК та серверів це здається неактуальним. Ще одна можливість, про яку я думаю, полягає в тому, що це може бути корисним / вирішальним для доступу до обладнання або, можливо, відображення областей пам’яті (наприклад, для виходу VGA тощо) ...?

Моє запитання: Чи існують конкретні домени, які не можуть або можуть бути дуже громіздкими для реалізації без цієї функції?

PS Будь ласка, скажіть, чи маєте ви краще ім'я для цього! ;)



@gnat Я думаю, я розумію, у чому твоя проблема. Це тому, що може бути декілька відповідей, правда? Ну, я розумію, що це питання може не відповідати способу роботи stackexchange, але я, чесно кажучи, не знаю, куди інакше запитати ...
Thomas Oltmann

1
@lxrec RTTI зберігається у vtable, а об'єкти зберігають лише покажчик на vtable. Крім того, типи мають RTTI, лише якщо вони вже мають vtable, оскільки вони мають функцію virtualчлена. Таким чином, RTTI ніколи не збільшує розмір будь-яких об'єктів, він лише робить бінарний більшим постійним.

3
@ThomasOltmann Кожен об'єкт, у якого є віртуальні методи, потребує vtable pointer. Без цього ви не можете мати функціональні віртуальні методи. Більше того, ви явно вирішили мати віртуальні методи (а значить, і vtable).

1
@ThomasOltmann Ви здаєтеся дуже розгубленим. Це не вказівник на об'єкт, який несе vtable вказівник, це сам об’єкт. Тобто, T *завжди однакового розміру і Tможе містити приховане поле, яке вказує на vtable. І жоден компілятор C ++ ніколи не вставляв Vtables в об'єкти, які їм не потрібні.

Відповіді:


5

Є кілька переваг, очевидним є час компіляції, щоб гарантувати, що такі параметри, як параметри функції, відповідають значенням, що передаються.

Але я думаю, ви запитуєте про те, що відбувається під час виконання.

Майте на увазі, що компілятор створить час виконання, який вбудовує знання про типи даних в операціях, які він виконує. Кожен фрагмент даних у пам’яті може не бути самоописаним, але код по суті знає, що це за дані (якщо ви правильно виконали свою роботу).

Під час виконання речі дещо інакше, ніж ви думаєте.

Наприклад, не вважайте, що при оголошенні uint16_t використовується лише два байти. Залежно від вирівнювання процесора та слова, він може займати 16, 32 або 64 біт на стеці. Ви можете виявити, що ваш масив шортів витрачає набагато більше пам’яті, ніж ви очікували.

Це може бути проблематично в певних ситуаціях, коли потрібно посилатись на дані з певними зрушеннями. Це відбувається під час спілкування між двома системами, що мають різні архітектури процесорів, або по бездротовому посиланню, або через файли.

C дозволяє задавати структури з деталізацією рівня бітів:

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

Ця структура має три байти, з коротким значенням, яке починається з непарного зміщення. Його також потрібно буде упакувати, щоб бути точно таким, яким ви його визначили. Інакше компілятор буде вирівнювати слова членами.

Компілятор генерує код за кадром, щоб витягти ці дані та скопіювати в реєстр, щоб ви могли робити корисні речі з ним.

Тепер ви можете бачити, що кожного разу, коли моя програма звертається до члена структури myMessage, вона буде знати, як саме її витягти та діяти над нею.

Це може стати проблематичним і важким для управління при спілкуванні між різними системами з різними версіями програмного забезпечення. Ви повинні ретельно розробити систему та код, щоб гарантувати, що обидві сторони мають точно однакове визначення типів даних. Це може бути досить складно в деяких умовах. Тут вам потрібен кращий протокол, який містить самоописування даних, таких як буфери протоколів Google .

І нарешті, ви хочете запитати, наскільки це важливо в середовищі робочого столу / сервера. Це дійсно залежить від того, скільки пам'яті ви плануєте використовувати. Якщо ви робите щось на зразок обробки зображень, ви можете використовувати великий об'єм пам'яті, що може вплинути на ефективність вашої програми. Це, безумовно, завжди викликає занепокоєння у вбудованому середовищі, де пам'ять обмежена, а віртуальної пам'яті немає.


2
"Ви можете виявити, що ваш масив шортів витрачає набагато більше пам'яті, ніж ви очікували." Це неправильно в C: Масиви гарантовано містять свої елементи безвідривно. Так, масив потрібно правильно вирівняти, як і одиничний short. Але це разова вимога до початку масиву, решта автоматично вирівнюється правильно в силу послідовності.
cmaster - відновити моніку

Також синтаксис для прокладки неправильний, він повинен бути uint8_t padding: 6;, як і перші два біти. Або, ясніше, просто коментар //6 bits of padding inserted by the compiler. Структура, як ви її написали, має розмір щонайменше дев'яти байтів, а не три.
cmaster - відновити моніку

9

Ви стикаєтесь з однією з єдиних причин, що це корисно: зіставлення зовнішніх структур даних. Сюди входять відеобуфери з картографічною пам'яттю, апаратні регістри тощо. Вони також включають дані, що передаються неушкодженими за межами програми, наприклад, SSL-сертифікати, IP-пакети, JPEG-зображення, і майже будь-яка інша структура даних, яка має стійке життя поза програмою.


5

C - мова низького рівня, майже портативний асемблер, тому його структури даних та мовні конструкції близькі до металевих (структури даних не мають прихованих витрат - крім обмежень, накладень , вирівнювання та розмірів, накладених апаратними засобами та ABI ). Тому C справді не має динамічного натискання тексту. Але якщо вам це потрібно, ви можете прийняти умову про те, що всі ваші значення - це сукупності, що починаються з відомостей про тип (наприклад, деякі enum...); Використання union-s і (для масиву-подібних речей) гнучкого елемента масиву в structякому також розмір масиву.

(при програмуванні на C ви несете відповідальність за визначення, документування та дотримання корисних конвенцій - особливо до- та після умов та інваріантів; також динамічне розподіл пам’яті вимагає freeроз'яснення домовленостей про те, хто повинен обробляти mallocзону пам’яті)

Таким чином, для представлення значень , які затиснуті цілі числа, або рядки, або яке - то схема -like символу , або вектори значень, ви будете концептуально використовувати позначений союз (реалізований у вигляді об'єднання покажчиків) -Завжди починаючи від типу виду -, наприклад:

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

Щоб отримати динамічний тип деякого значення

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

Ось "динамічний склад" для векторів:

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

і "безпечний доступ" всередині векторів:

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

Зазвичай ви визначаєте більшість коротких функцій вище, як static inlineу деяких файлах заголовка.

До речі, якщо ви можете використовувати сміттєзбірник Boehm, ви зможете кодувати досить легко в якомусь вищому (але небезпечному) стилі, і кілька інтерпретаторів схем робляться саме так. Варіантний конструктор вектор може бути

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

і якщо у вас є три змінні

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

ви можете створити з них вектор за допомогою make_vector(3,v1,v2,v3)

Якщо ви не хочете використовувати сміттєзбірник Boehm (або спроектувати свій власний), ви повинні бути дуже обережними щодо визначення деструкторів та документування того, хто, як і коли має бути free-d; дивись цей приклад. Таким чином, ви можете використовувати malloc(але потім протестувати на його збій) замість GC_MALLOCвище, але вам потрібно ретельно визначити та використовувати деяку функцію деструктораvoid destroy_value(value_t)

Міцність C полягає в тому, щоб бути достатньо низьким, щоб зробити можливим подібний код та визначити власні конвенції (зокрема, до вашого програмного забезпечення).


Я думаю, ви неправильно зрозуміли моє запитання. Я не хочу динамічного введення в C. Мені було цікаво, чи ця специфічна властивість C має якусь практичну користь.
Томас Ольтман

Але яке саме властивість C ви хочете отримати? Структури даних C близькі до металевих, тому не мають прихованих витрат (крім вирівнювання та обмеження розмірів)
Basile Starynkevitch

Саме так: /
Томас Олтманн

C був винайдений як мова низького рівня, але коли ввімкнено оптимізацію на компіляторах, як gcc, обробляють мову, яка використовує синтаксис низького рівня, але не забезпечує надійного доступу низького рівня до наданих платформою поведінкових гарантій. Для використання malloc та memcpy потрібен sizeof, але використання для вигадливіших обчислень адреси може не підтримуватися в "сучасній" С.
supercat
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.