Какие темы, касающиеся языка C, труднее всего изучить?



Одна из моих хороших подруг - преподаватель специального образования в одной из местных средних школ. На днях в разговоре она сказала нечто, что потрясло мой мир. Она начала рассказывать о классе математики, который она разрабатывала, в котором ключевым разделом был “unit zero”, она говорила со своими учениками о “t the language and expectations of academic life”. Она не считала само собой разумеющимся, что они понимают такие вещи, как “какая’ разница между… “ и “describe a case… “ или “combining…” или “show…” или “compare… “ или “estimate…”. Вместо этого она подробно описала все это, чтобы дети, сталкиваясь с этими вещами в будущем, могли ориентироваться, и, что самое важное, у них был глоссарий, к которому они могли обратиться.

Размышляя над этим вопросом, мне стало ясно, что самая сложная тема в изучении языка C - это не “in C” как таковой,… а понимание мышления и основных вычислительных архитектур, которые привели нас к конструкциям в C, и в частности к схеме управления памятью и библиотеке времени выполнения C.

Язык C был разработан в ответ на появление архитектур с байтовой адресацией. До появления этого нового класса машин машинные слова были самой низкоуровневой адресуемой единицей памяти. Язык C - это дочерний язык программирования B с байтовой адресацией, который стал результатом работы Кена Томпсона (а позже Денниса Ричи) над проектом MULTICS на языке BCPL. Язык B был упрощен из BCPL и был описан как “BCPL семантика с Algol синтаксисом”, который может быть довольно точно модифицирован для описания языка C, если вы добавите “плюс однобайтовые типы данных”.

Понимание указателей в C широко рассматривается как “t самая трудная часть” начинающими программистами, которые впервые обращаются к языку, но В ТЕ ВРЕМЕНА это действительно не было большой проблемой. Программисты должны были полностью знать архитектуру и режимы адресации памяти систем, на которых они выполняли свои программы. Так, если вы выделяли двухмерный массив данных, скажем, int, вы знали, что у вас есть N раз M единиц int. И математика “пойнтеров” просто имела смысл,— если вам нужен элемент из X-й строки и Y-го столбца,… вы должны были знать, где искать. Так для массива(a,b) нужно было взять количество элементов в строке, умноженное на количество строк, чтобы получить начало строки, в которой нужно найти нужный элемент.

Поэтому, когда вы писали “result = array(a,b)” это не было гигантской загадкой. И это было лишь немного сложнее, когда размер элемента был’ не машинным словом (что является родным sizeof(int)).

Вы также знали - потому что так оно и было - что ничто магическое не помешает вам выбрать значения a и b, которые были бы больше, чем выделенные значения N и M. И вы не ожидали обратного, потому что это не было обычной особенностью языков до Паскаля и его преемников.

Вы также обычно знали такие вещи, как индексированный режим адресации, когда вы’d используете регистр для хранения указателя на элемент, который затем разыменовывается и оперируется со значением в другом регистре. Опять же, такая конструкция инструкций позволяет легко перенести концепцию на использование указателей в языке Си.

Другая вещь, которую вы’d знаете, это откуда взялась адресуемая память. В ранних системах у вас было фиксированное пространство без управления памятью. Обычно 16 бит, иногда больше, вы’d имели возможность обратиться к фиксированному количеству элементов памяти с абсолютным адресом. И если там была физическая память, вы’ получали ее содержимое; в противном случае вы’ получали нули.

Когда появилось управление памятью, у вас’ было виртуальное пространство - ваша программа видела 16 бит адресного пространства, но оно могло находиться в более крупном физическом пространстве памяти. Эффективное управление этим пространством означало, что вы не должны выделять всю память сразу, потому что вы можете использовать только крошечный кусочек. Таким образом, среда выполнения C развилась из парадигм проектирования UNIX, основанных на архитектуре PDP-11. У вас’d была статически выделенная память в нижней части адресного пространства (низкие числа), которая могла быть увеличена путем обращения к ней через вызовы выделения (sbrk() на уровне ядра, malloc() на уровне библиотеки), и пространство стека, которое увеличивалось сверху вниз (высшие числа).

Это было задокументировано, и от вас ожидалось, что вы будете знать и понимать это, что быстро привело к пониманию того, как управлять памятью, которую будет использовать ваша программа, вручную. Настоящей сборки мусора не было, и вы учились - или ваши программы неожиданно умирали.

В наши дни? Со страничной виртуальной памятью и 64-битными адресными пространствами? Да, это кажется ужасно устаревшим и ненужным - но все еще существует бесконечное количество способов, которыми вы можете взорвать себя, если не понимаете распределение памяти во время выполнения.

Современные машины с огромным объемом памяти и полностью виртуализированной страничной памятью, а также с гораздо более сложными наборами инструкций и сопутствующими оптимизациями (внепорядковое выполнение, предиктивная выборка, многоуровневый кэш) означают, что сложность возросла. Такой "голый" язык, как C, может быть полезен, но в то же время, предположения, которые он делает в отношении того, насколько хорошо программист знает механику, которая лежит в основе реализации, больше не действительны.

C, как и B и BCPL до них, являются продуктами более простого времени, когда основы вычислительного механизма были гораздо более открытыми и хорошо изученными.

Если вы хотите упростить самые сложные разделы языка C, не ищите хаков и трюков. Ищите понимание того, что происходит под поверхностью. Тогда все встанет на свои места и обретет смысл.

204   0  
    Ничего не найдено.

Добавить ответ:
Отменить.