Чому масиви С не відстежують їх довжину?


77

Що було міркуванням про те, щоб явно не зберігати довжину масиву з масивом у C?

Як я це бачу, є надзвичайно багато причин для цього, але не дуже багато в підтримку стандарту (C89). Наприклад:

  1. Доступна довжина в буфері може запобігти перевищенню буфера.
  2. Стиль Java arr.lengthодночасно зрозумілий і не дозволяє програмісту підтримувати багато ints на стеку, якщо він має справу з декількома масивами.
  3. Параметри функції стають більш зухвалими.

Але, мабуть, найбільш мотивуюча причина, на мою думку, полягає в тому, що зазвичай жоден простір не економиться без збереження довжини. Я б ризикну сказати, що більшість застосувань масивів передбачає динамічне розподіл. Щоправда, можуть бути випадки, коли люди використовують масив, виділений у стеку, але це лише один виклик функції * - стек може обробляти додатково 4 або 8 байтів.

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

Єдине , що я можу думати на іншій стороні, що ні трекінгу довжина не може бути зроблено компілятори простіше, але не , що набагато простіше.

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


6
Я припускаю, що можна було б стверджувати, що при включенні C із застосуванням структур як типів параметрів та повернених значень він повинен містити синтаксичний цукор для "векторів" (чи будь-якої назви), який під ним повинен бути структурою з довжиною і або масивом, або вказівником на масив . Підтримка мовного рівня для цієї загальної конструкції (також якщо вона передається як окремі аргументи, а не окрема структура) врятувала б незліченні помилки та спростила стандартну бібліотеку.
Гайд

3
Ви також можете зрозуміти, чому Паскаль не є моєю улюбленою мовою програмування Розділ 2.1, щоб бути зрозумілим.

34
Хоча всі інші відповіді мають деякі цікаві моменти, я думаю, що підсумок полягає в тому, що C був написаний, щоб програмісти на мові складання змогли б простіше писати код і мати його портативно. Зважаючи на це, наявність автоматичної довжини масиву, що зберігається З масивом, було б неприємністю, а не недоліком (як це було б і з іншими приємними бажаннями з цукерками). Ці функції сьогодні здаються приємними, але тоді насправді часто виникала боротьба за втиснення ще однієї байтової програми або даних у вашу систему. Марнотратне використання пам'яті суттєво обмежило б прийняття C.
Данк

6
Справжню частину вашої відповіді вже багато разів відповідали так, як я мав би, але я можу витягнути інший момент: "Чому не malloc()можна запитувати розмір області редакції портативно?" Це річ, яка мене змушує задуматися кілька разів.
glglgl

5
Голосування за повторне відкриття. Десь є чомусь, навіть якщо це просто "K&R не думав про це".
Теластин

Відповіді:


106

C масиви відстежують їх довжину, оскільки довжина масиву є статичною властивістю:

int xs[42];  /* a 42-element array */

Зазвичай ви не можете запитувати цю довжину, але цього не потрібно, оскільки це все одно статично - просто оголосіть макрос XS_LENGTHна довжину, і ви закінчите.

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

Інша справа, що покажчики не потребують зберігання, крім самої адреси пам'яті. C дозволяє нам накидати цілі числа на покажчики, вказівники на інші вказівники та обробляти вказівники так, ніби вони були масивами. Роблячи це, C недостатньо божевільний, щоб створити деяку довжину масиву, але, здається, довіряє девізу Spiderman: з великою силою програміст сподівається виконати велику відповідальність за відстеження довжин та переливів.


13
Я думаю, ви хочете сказати, якщо я не помиляюся, що компілятори C відслідковують статичну довжину масиву. Але це не корисно для функцій, які просто отримують вказівник.
VF1

25
@ VF1 так. Але важливо те, що масиви і покажчики різні речі в C . Якщо припустити, що ви не використовуєте будь-яких розширень компілятора, ви не можете передавати масив самій функції, але ви можете передавати вказівник та індексувати покажчик так, ніби це масив. Ви фактично нарікаєте, що покажчики не мають доданої довжини. Вам слід скаржитися, що масиви не можуть бути передані як аргументи функції, або що масиви деградують на покажчики неявно.
амон

37
"Зазвичай ви не можете запитувати цю довжину" - насправді ви можете, це оператор sizeof - sizeof (xs) поверне 168, припускаючи, що int мають чотири байти. Щоб отримати 42, зробіть: sizeof (xs) / sizeof (int)
tcrosley

15
@tcrosley Це працює лише в межах декларації масиву - спробуйте передати xs як параметр іншій функції, а потім подивіться, який розмір (xs) дає вам ...
Gwyn Evans

26
@GwynEvans знову: покажчики не є масивами. Отже, якщо ви "передаєте масив як парам в іншу функцію", ви передаєте не масив, а покажчик. Стверджувати, що sizeof(xs)де xsмасив був би чимось іншим в іншій області, явно неправдиво, оскільки конструкція C не дозволяє масивам залишати їх сферу застосування. Якщо sizeof(xs)де xsмасив відрізняється від того, sizeof(xs)де xsє вказівник, це не дивно, оскільки ви порівнюєте яблука з апельсинами .
амон

38

Багато цього стосувалося комп’ютерів, наявних у той час. Не тільки складена програма повинна працювати на комп'ютері з обмеженими ресурсами, але, що ще важливіше, сам компілятор повинен був працювати на цих машинах. У той час, коли Томпсон розробляв C, він використовував PDP-7, маючи 8 кб оперативної пам'яті. Складні мовні функції, які не мали негайного аналога на фактичному машинному коді, просто не були включені до мови.

Уважне прочитання історії C дає більше розуміння вищесказаному, але це було не зовсім наслідком машинних обмежень:

Більше того, мова (С) демонструє значну силу для опису важливих понять, наприклад, векторів, довжина яких змінюється під час виконання, лише з кількома основними правилами та умовами. ... Цікаво порівняти підхід С з двома майже сучасними мовами, Алголем 68 та Паскалем [Jensen 74]. Масиви в Algol 68 або мають фіксовану межу, або "гнучкі": потрібен значний механізм як у мовному визначенні, так і в компіляторах, щоб вмістити гнучкі масиви (і не всі компілятори повністю реалізувати їх.) Оригінальний Pascal мав лише фіксованого розміру масиви та рядки, і це виявилося обмежуючим [Керніган 81].

Масиви С по суті є більш потужними. Додавання меж до них обмежує те, для чого програміст може їх використовувати. Такі обмеження можуть бути корисними для програмістів, але обов'язково також є обмежуючими.


4
Це майже нівелює оригінальне питання. Це і той факт, що C тримався свідомо «легким дотиком», коли справа стосувалася перевірки того, що робить програміст, як частина, що робить його привабливим для написання операційних систем.
ClickRick

5
Відмінне посилання, вони також явно змінили, зберігаючи довжину рядків, щоб використовувати роздільник to avoid the limitation on the length of a string caused by holding the count in an 8- or 9-bit slot, and partly because maintaining the count seemed, in our experience, less convenient than using a terminator- ну так для цього :-)
Voo

5
Непідтримані масиви також підходять до підходу голого металу C. Пам'ятайте, що книга K&R C має менше 300 сторінок з мовним посібником, довідкою та списком стандартних дзвінків. Моя книга O'Reilly Regex майже вдвічі довша за K&R C.
Майкл Шопсін

22

Ще в той день, коли було створено C, і додаткові 4 байти місця для кожної рядки, незалежно від того, наскільки короткий був би досить марно!

Є ще одне питання - пам’ятайте, що C не орієнтований на об’єкт, тому якщо ви зробите префікс довжини всіх рядків, його потрібно було б визначити як внутрішній тип компілятора, а не char*. Якби це був спеціальний тип, ви б не змогли порівнювати рядок з постійною рядком, тобто:

String x = "hello";
if (strcmp(x, "hello") == 0) 
  exit;

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

Я думаю, що в кінцевому підсумку вони просто не обрали спосіб префіксації довжини на відміну від Паскаля.


10
Перевірка меж також вимагає часу. Тривіальне в сьогоднішніх умовах, але те, на що люди звертали увагу, коли піклувалися про 4 байти.
Стівен Бернап

18
@StevenBurnap: це не так банально навіть сьогодні, якщо ви знаходитесь у внутрішньому циклі, який перевищує кожен піксель зображення в 200 МБ. Взагалі, якщо ви пишете C, ви хочете швидко піти , і ви не хочете витрачати час на марну перевірку на кожну ітерацію, коли ваш forцикл вже був налаштований на дотримання меж.
Matteo Italia

4
@ VF1 "назад у добу" це могло бути два байти (DEC PDP / 11 хтось?)
ClickRick

7
Це не просто "назад у день". Програмне забезпечення, на яке C орієнтоване, як "портативна мова збірки", наприклад, ядра ОС, драйвери пристроїв, вбудоване програмне забезпечення в реальному часі тощо. витрачати півдесятка інструкцій на перевірку меж має значення, і, у багатьох випадках, вам потрібно бути "поза межами" (як ви могли написати відладчик, якщо ви не могли випадково отримати доступ до іншого сховища програм?).
Джеймс Андерсон

3
Це насправді досить слабкий аргумент, враховуючи, що BCPL міркував тривалість аргументів. Як і Паскаль, хоча це було обмежено одним словом, так загалом лише 8 або 9 біт, що було трохи обмежує (це також виключає можливість спільного використання частин рядків, хоча ця оптимізація, ймовірно, була занадто розвиненою на той час). І оголосити рядок як структуру з довжиною, за якою слід масив, справді не знадобиться спеціальна підтримка компілятора ..
Voo

11

У C будь-яке суміжне підмножина масиву також є масивом і ним можна керувати як таким. Це стосується операцій читання та запису. Ця властивість не зберігатиметься, якщо розмір зберігався явно.


6
"Дизайн був би іншим" - це не причина проти того, що дизайн відрізнявся.
VF1

7
@ VF1: Ви коли-небудь програмували в Standard Pascal? Здатність C бути достатньо гнучкими з масивами була величезним поліпшенням порівняно зі збіркою (ні про яку безпеку) і першим поколінням безпечних мов (overkill typesafety, включаючи точні межі масиву)
MSalters

5
Ця здатність розрізати масив справді є масовим аргументом для дизайну C89.

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

3
Існує одна цікава альтернатива дизайну, яка дозволяє нарізати: Не зберігайте довжину поряд з масивами. Для будь-якого вказівника на масив збережіть довжину разом із вказівником. (Коли у вас просто є справжній масив С, розмір - це константа часу компіляції і доступний компілятору.) Це займає більше місця, але дозволяє нарізати, зберігаючи довжину. &[T]Наприклад, іржа робить це для типів.

8

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

void ClearTwoElements(int *ptr)
{
  ptr[-2] = 0;
  ptr[2] = 0;
}
void blah(void)
{
  static int foo[10] = {1,2,3,4,5,6,7,8,9,10};
  ClearTwoElements(foo+2);
  ClearTwoElements(foo+7);
  ClearTwoElements(foo+1);
  ClearTwoElements(foo+8);
}

єдиний спосіб, коли код міг би прийняти перший виклик, ClearTwoElementsале відхилити другий, - це ClearTwoElementsметод отримання інформації, достатньої для того, щоб знати, що в кожному конкретному випадку він отримує посилання на частину масиву fooна додаток до того, щоб знати, на яку частину. Це, як правило, подвоює вартість передачі параметрів вказівника. Крім того, якщо кожному масиву передує вказівник на адресу, що знаходиться в кінці кінця (найефективніший формат для перевірки), оптимізований код для ClearTwoElementsцього, ймовірно, стане чимось на зразок:

void ClearTwoElements(int *ptr)
{
  int* array_end = ARRAY_END(ptr);
  if ((array_end - ARRAY_BASE(ptr)) < 10 ||
      (ARRAY_BASE(ptr)+4) <= ADDRESS(ptr) ||          
      (array_end - 4) < ADDRESS(ptr)))
    trap();
  *(ADDRESS(ptr) - 4) = 0;
  *(ADDRESS(ptr) + 4) = 0;
}

Зауважте, що виклик методу, як правило, може абсолютно законно передавати покажчик на початок масиву або останній елемент на метод; лише якщо метод намагається отримати доступ до елементів, які виходять за межі масиву, що передається, такі вказівники спричинили б якісь проблеми. Отже, викликаний метод повинен був спершу переконатися, що масив був достатньо великим, щоб арифметика вказівника для перевірки його аргументів не виходила за межі, а потім виконати деякі розрахунки вказівника для перевірки аргументів. Час, витрачений на таку перевірку, швидше за все перевищить витрати, витрачені на виконання будь-якої реальної роботи. Крім того, метод, ймовірно, може бути більш ефективним, якби він був написаний і названий:

void ClearTwoElements(int arr[], int index)
{
  arr[index-2] = 0;
  arr[index+2] = 0;
}
void blah(void)
{
  static int foo[10] = {1,2,3,4,5,6,7,8,9,10};
  ClearTwoElements(foo,2);
  ClearTwoElements(foo,7);
  ClearTwoElements(foo,1);
  ClearTwoElements(foo,8);
}

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


Якщо масиви мали розмір часу виконання, то вказівник на масив кардинально відрізнявся від вказівника на елемент масиву. Latter може взагалі не бути безпосередньо конвертованим у колишній (без створення нового масиву). []синтаксис все ще може існувати для покажчиків, але це було б інакше, ніж для цих гіпотетичних "реальних" масивів, і проблема, яку ви описуєте, ймовірно, не існувала б.
Гайд

@hyde: Питання в тому, чи слід дозволити арифметику на покажчиках, адреса базових об'єктів яких невідома. Також я забув ще одну складність: масиви всередині структур. Думаючи про це, я не впевнений, що не було б такого типу вказівника, який міг би вказувати на масив, що зберігається в структурі, не вимагаючи, щоб кожен вказівник включав не тільки адресу самого вказівника, але і верхній і нижній юридичний діапазони, до яких він може отримати доступ.
supercat

Точка перетину. Я думаю, що це все ще зводиться до відповіді Амона.
VF1

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

@hyde: Якщо достатньо змінити семантику мови, можливо, масиви містять пов’язану довжину, хоча масиви, що зберігаються в структурах, становлять певні труднощі. З такою семантикою, перевірка меж масиву була б корисною лише в тому випадку, якщо ця сама перевірка застосовується до покажчиків елементів масиву.
supercat

7

Однією з фундаментальних відмінностей між C та більшості інших мов третього покоління та всіх останніх мов, які мені відомі, є те, що C не був розроблений для того, щоб полегшити чи безпечніше життя програміста. Це було розроблено з розрахунком, що програміст знає, що вони роблять, і хоче зробити саме це і тільки це. Це не робить нічого "поза лаштунками", щоб не отримати сюрпризів. Навіть оптимізація рівня компілятора не є обов'язковою (якщо ви не використовуєте компілятор Microsoft).

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


3
C не був настільки "розроблений", як розвивався. Спочатку декларація на зразок int f[5];не створюватиметься fяк масив із п’яти елементів; натомість це було рівнозначно int CANT_ACCESS_BY_NAME[5]; int *f = CANT_ACCESS_BY_NAME;. Колишню декларацію можна було б обробити без того, щоб компілятор мав дійсно "розуміти" масиви; він просто повинен був вивести директиву асемблера, щоб розподілити простір, а потім міг забути, що fколи-небудь було пов'язане з масивом. Звідси випливає непослідовна поведінка типів масивів.
supercat

1
Виявляється, жоден програміст не знає, що вони роблять до тієї міри, якої вимагає C.
CodesInChaos

7

Коротка відповідь:

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

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

retval = my_func(my_array, my_array_length);

Або ви можете використовувати структуру з вказівником і довжиною, або будь-яке інше рішення.

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

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


1
+1 "І якщо весь код, який ви пишете, вже знає довжину масиву, вам взагалі не потрібно передавати довжину як змінну."
林果 皞

Якби тільки мова вказівника + структура довжини була виписана в мову та стандартну бібліотеку. Стільки отворів у безпеці вдалося уникнути.
CodesInChaos

Тоді це насправді не було б. Є й інші мови, які роблять це. З отримуєш ти низький рівень.
thomasrutter

C був винайдений як мова програмування низького рівня, і багато діалектів досі підтримують програмування на низькому рівні, проте багато авторів-компіляторів віддають перевагу діалектам, які насправді не можна назвати мовами низького рівня. Вони дозволяють і навіть вимагають синтаксису низького рівня, але потім намагаються вивести конструкції вищого рівня, поведінка яких може не відповідати семантиці, що передбачається синтаксисом.
supercat

5

Проблема додаткового зберігання - це проблема, але, на мою думку, другорядне. Зрештою, більшу частину часу вам потрібно буде відстежувати довжину так чи інакше, хоча Амон вказував на те, що її часто можна відстежувати статично.

Більша проблема полягає в тому, де зберігати довжину і як довго її робити. Немає одного місця, яке працює у будь-яких ситуаціях. Можна сказати, просто збережіть довжину в пам'яті безпосередньо перед даними. Що робити, якщо масив не вказує на пам'ять, а щось на зразок буфера UART?

Виявлення довжини дозволяє програмісту створити власні абстракції для відповідної ситуації, і є багато готових бібліотек, доступних для загальної справи. Справжнє запитання: чому ці абстракції не використовуються в чутливих до безпеки додатках?


1
You might say just store the length in the memory just before the data. What if the array isn't pointing to memory, but something like a UART buffer?Не могли б ви пояснити це трохи більше? Також те, що може траплятися занадто часто або це лише рідкісний випадок?
Махді

Якби я його сконструював, аргумент функції, написаний так T[], не був би еквівалентним, T*а передав би функції вказівника та розміру функції. Масиви фіксованого розміру можуть розпадатись на такий фрагмент масиву, а не розкладатися на покажчики, як це робиться в C. Основна перевага цього підходу полягає не в тому, що він є безпечним сам по собі, але це умова, за яким все, включаючи стандартну бібліотеку, може будувати.
CodesInChaos

1

Із розвитку мови С :

Структури, здавалося, повинні інтуїтивно відображатися на пам'яті в машині, але в структурі, що містить масив, не було хорошого місця, щоб сховати вказівник, що містить базу масиву, ні будь-який зручний спосіб домовитись, щоб це було ініціалізовано. Наприклад, записи в каталогах ранніх систем Unix можуть бути описані в C як
struct {
    int inumber;
    char    name[14];
};
Я хотів, щоб структура не просто характеризувала абстрактний об’єкт, а й описувала колекцію бітів, яку можна прочитати з каталогу. Де компілятор міг приховати покажчик на nameте, що вимагала семантика? Навіть якщо структури вважали більш абстрактними, а простір для покажчиків можна було якось приховати, як я можу впоратися з технічною проблемою правильної ініціалізації цих покажчиків при розподілі складного об'єкта, можливо, такого, який вказав структури, що містять масиви, що містять структури, на довільну глибину?

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

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

Також подумайте про багатовимірні масиви; де б ви зберігали метадані довжини для кожного виміру, щоб ви все одно могли пройти по масиву з чимось подібним

T *p = &a[0][0];

for ( size_t i = 0; i < rows; i++ )
  for ( size_t j = 0; j < cols; j++ )
    do_something_with( *p++ );

-2

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

У наведеному нижче коді копіюються деякі дані з src в dst в шматки розміру int, не знаючи, що це насправді рядок символів.

char src[] = "Hello, world";
char dst[1024];
int *my_array = src; /* What? Compiler warning, but the code is valid. */
int *other_array = dst;
int i;
for (i = 0; i <= sizeof(src)/sizeof(int); i++)
    other_array[i] = my_array[i]; /* Oh well, we've copied some extra bytes */
printf("%s\n", dst);

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


2
Я не думаю, що ти відповів на це питання.
Роберт Харві

2
Те, що ви сказали, є правдою, але людина, яка запитує, хоче знати, чому це так.

9
Пам’ятайте, одне з прізвиськ для C - це «портативна збірка». Хоча новіші версії стандарту додають концепцій вищого рівня, в його основі він складається з простих конструкцій низького рівня та інструкцій, які є загальними для більшості нетривіальних машин. Це визначає більшість дизайнерських рішень, прийнятих мовою. Єдині змінні, що існують під час виконання, - це цілі числа, плавці та покажчики. Інструкції включають арифметику, порівняння та стрибки. Насправді все інше - це тонкий шар, побудований поверх цього.

8
Неправильно сказати, що у C немає масивів, враховуючи, як ви дійсно не можете генерувати той самий бінарний файл з іншими конструкціями (ну, принаймні, не, якщо ви розглядаєте використання #defines для визначення розмірів масиву). Масиви на C - це "суцільні послідовності даних", нічого приємного в цьому. Використання покажчиків на зразок масивів - це синтаксичний цукор (замість явної арифметики вказівника), а не самі масиви.
Гайд

2
Так, розглянути цей код struct Foo { int arr[10]; }. arrце масив, а не вказівник.
Стівен Бернап
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.