СИ-ТЕСТ: 0x10 ЛУЧШИХ ВОПРОСОВ ДЛЯ ТЕХ, КТО ХОТЕЛ БЫ СТАТЬ ПРОГРАММИСТОМ ВСТРАИВАЕМЫХ СИСТЕМ

19/09/2010 - 20:57

   С точки зрения интервьюируемого вы можете много узнать о том, кто написал этот тест.  Составлен ли тест ради того, чтобы показать насколько хорошо автор знает мельчайшие подробности ANSI-стандарта, вместо того чтобы проверить реальные знания? Проверяет ли он абсурдные знания, такие как коды определенных ASCII символов? Имеют ли вопросы тенденцию к выявлению ваших знаний системных вызовов и стратегий распределения памяти, указывая на то, что автор мог заниматься программированием компьютеров, вместо программирования встраиваемых систем? Если какие-либо из перечисленных вещей имеют место, то я бы серьёзно задумался – хочу ли я такую работу.

  С точки зрения работодателя, тест может много рассказать о кандидате. В первую очередь, тест может определить уровень знания кандидатом языка Си. Впрочем,  также интересно бывает посмотреть, как люди отвечают на вопросы, на которые они не знают ответа. Делают ли они осмысленный выбор, подкреплённый хорошим чувством интуиции, или же просто пытаются угадать? Занимают ли они оборонительную позицию, когда оказываются в затруднительном положении, или проявляют к проблеме живой интерес и видят в ней возможность узнать что-то новое? Я нахожу эту информацию такой же полезной, как и непосредственно саму работу во время теста.

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

   Этот тест может быть предложен широкому кругу кандидатов. Большинство претендентов начального уровня едва ли справятся с ним, в то время как закалённые ветераны должны пройти его на отлично. Очки не присуждаются, я стремлюсь произвольно оценивать   вопросы. Однако, если вы собираетесь приспособить этот тест для ваших собственных потребностей, то можете спокойно присуждать баллы.

Препроцессор

1. Используя директиву #define, как бы вы описали именованную константу, которая возвращает число секунд в году? Високосными годами следует пренебречь.

#define SECONDS_PER_YEAR (60UL * 60UL * 24UL * 365UL)

 Здесь я смотрю на несколько моментов:

(а) Базовое знание синтаксиса #define (т.е. отсутствие точки с запятой в конце, необходимость заключать в круглые скобки и т.д.).

(b) Правильный выбор имени - с применением заглавных букв и подчёркиванием.

(c) Понимание того, что препроцессор будет вычислять для вас константное выражение.

(d) Понимание того, что выражение переполнит integer аргумент на 16-и битной машине -  а следовательно потребность в L, указывающей компилятору обращаться с выражением  как с Long.

(е) А если вы в добавок написали выражение с UL (обозначающее unsigned long), то вы отлично начали тест, потому что показываете, что знаете об опасности типов со знаками и без, и запомните - первое впечатление считается!

2. Напишите «стандартный» макрос MIN. То есть, макрос, который берет два аргумента и возвращает меньший из них.

#define MIN(A,B)       ((A) <=  (B) ? (A) : (B))

  Цель этого вопроса - проверить следующее:

(а) Умение использовать директиву #define для написания макросов. Это важно, потому что до того, как inline оператор стал частью Си стандарта, макросы были единственным способом генерирования встраиваемого кода. А такой код часто бывает необходим для достижения требуемого уровня производительности.

(b) Знание троичного условного оператора. Он используется в Си, потому что позволяет компилятору производить потенциально более оптимальный код, чем последовательность if-else. Производительность обычно является важной составляющей  во встраиваемых  системах, поэтому необходимо знать и уметь использовать эту конструкцию.

(с) Понимание необходимости заключать аргументы макросов в скобки.

(d) Я также использую этот вопрос, чтобы начать разговор о побочных эффектах макросов. Например, о том, что происходит, когда вы пишите такой код:

least = MIN(*p++, b);

3. Каково назначение директивы препроцессора #error?

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

Бесконечные циклы

4. При программировании  встраиваемых систем часто используются  бесконечные циклы. Как реализовать бесконечный цикл в Си?

Существует несколько решений этой проблемы. Я предпочитаю такое:

while(1)
{
   …
}

Другая общепринятая конструкция выглядит так:

for( ; ; )
{
   …
}

  Лично я не люблю эту конструкцию, потому что такой синтаксис не объясняет, что происходит. Если кандидат предлагает именно это решение, я пытаюсь выяснить, чем он обосновывает свои действия. Если ответ сводится к тому, что - «Меня научили так делать, и я никогда об этом с тех пор не думал» - это говорит не в пользу кандидата. С другой стороны, если он заявляет, что Керниган и Ритчи предпочитали этот метод, и это единственный способ для бесконечного цикла пройти контроль на соответствие стандартам, то он получает дополнительные очки.

Третье решение заключается в использовании goto:

Loop:

goto Loop;

   Кандидаты, которые предлагают этот вариант, являются либо программистами на языке ассемблера, либо они оторванные от жизни программисты Бейсика/Фортрана, ищущие новое поле для деятельности.

Объявление данных

5. Используя переменную «a», запишите объявления для:

(а) Целого
(b) Указателя на целое
(с) Указателя на указатель на целое
(d) Массива из десяти целых
(е) Массива из десяти указателей на целые
(f) Указателя на массив из десяти целых
(g) Указателя на функцию, которая принимает целочисленный аргумент и возвращает целое
(h) Массива из десяти указателей на функции, которая принимает целочисленный аргумент и возвращает целое

Ответы:

(a)   int a;                 // Целое

(b)   int *a;               // Указатель на целое

(c)   int **a;             // Указатель на указатель на целое

(d)   int a[10];          // Массив из десяти целых

(e)   int *a[10];        // Массив из десяти указателей на целые

(f)    int (*a)[10];     // Указатель на массив из десяти целых

(g)   int (*a)(int);     // Указатель на функцию, которая берет целый аргумент и возвращает целое

(h)   int (*a[10])(int); // Массив из десяти указателей на функции, которые берут целый аргумент и возвращают целое

   Люди часто утверждают, что на некоторые из этих вопросов они обычно ищут ответы  в  руководствах – согласен. Во время написания этой статьи я сверялся с руководствами, чтобы убедиться, что синтаксис является верным. Однако во время своего интервью я обычно ожидаю, что мне зададут подобный вопрос. Поэтому я должен быть уверен, что у меня есть ответы, по крайней мере, на несколько часов интервью. Кандидаты, которые не знают ответов (или, по крайней мере, большую их часть), просто не готовы к интервью. Если они не могут быть готовы к интервью, к чему они могут быть готовы вообще?

Static

6. В каких случаях используется ключевое слово static?

   Полностью отвечают на этот вопрос довольно редко. Спецификатор static в языке Си используется в трёх случаях:

(а) Переменная, описанная внутри тела функции как статическая, сохраняет свое значение между вызовами функции.

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

(с) Функции, описанные внутри модуля как статические, могут быть вызваны только другими функциями из этого модуля. То есть, область видимости функции локализована модулем, внутри которого она описана.

   Большинство кандидатов отвечают правильно на первую часть. Умеренное число кандидатов справляется со второй частью, ну и небольшое количество понимают ответ (с). Это серьёзный недостаток кандидата, если он не понимает важность и преимущества ограничения области видимости данных и кода.

Const

7. Что означает ключевое слово const?

   Как только интервьюируемый говорит: «Const - значит константа», я понимаю, что имею дело с непрофессионалом. Дэн Сакс в прошлом году дал исчерпывающее объяснение спецификатору const, так что каждый читатель ESP должен быть досконально ознакомлен с тем, что const может сделать для вас и чего он не может. Если вы не читали эту рубрику, достаточно будет сказать, что const означает «только для чтения». Хотя этот ответ не совсем справедливо отражает предмет разговора, я бы принял его в качестве правильного.

   Если кандидат даст правильный ответ, то я задам ему следующие дополнительные вопросы:

Что означают следующие объявления?

const int a;

int const a;

const int *a;

int * const a;

const int * const a;

   Первые два объявления означают одну и ту же вещь, а именно: «а» - это целочисленная константа (только для чтения). Третье означает, что «а» является указателем на целочисленную константу. Четвёртое описывает «а» как константный указатель на целое. И последнее объявление — константный указатель на целочисленную константу.

   Если кандидат правильно ответит на эти вопросы, я буду впечатлён.

   В данном случае он может поинтересоваться, почему я делаю такой упор на спецификатор const, так как очень легко написать правильно функционирующую программу, не используя его ни разу. Существует несколько причин:

(а) Использование спецификатора const сообщает полезную информацию тому, кто читает ваш код. Фактически, объявление параметра как const, говорит пользователю о его предполагаемом использовании. Если вы когда-нибудь тратили много времени, устраняя неразбериху, оставленную другими людьми, то вы быстро научитесь ценить эту дополнительную информацию. (Конечно, программисты, использующие const, редко оставляют после себя путаницу, которую приходится устранять другим…)

(b) Const сообщает оптимизатору некоторую дополнительную информацию, что потенциально позволяет генерировать более оптимальный код.

(с) Код, в котором используется спецификатор const, проявляет тенденцию к меньшему количеству ошибок.

Volatile

8. Что означает ключевое слово volatile? Приведите три различных примера его использования.

   Ключевое слово volatile информирует компилятор о том, что переменная может быть изменена не только из текущего выполняемого кода, но и из других мест. Тогда компилятор будет избегать определенных оптимизаций этой переменной.

Примеры volatile переменных:

(а) Регистры в периферийных устройствах (например, регистры состояния)

(b) Глобальные переменные, используемые в обработчиках прерываний.

(с) Глобальные переменные, используемые совместно несколькими задачами в многопотоковом приложении.

  Если кандидат не знает ответ на этот вопрос, он не получит работу. Я считаю, что это наиболее существенный вопрос, который позволяет отличить «Си-программиста» от «программиста встраиваемых систем». Программисты встраиваемых систем сталкиваются с аппаратными средствами, прерываниями, ОСРВ, и тому подобным. Все эти вещи требуют использования volatile переменных. Непонимание идеи спецификатора volatile приведет к катастрофе.

   Исходя из (сомнительного) предположения, что интервьюируемый ответит на этот вопрос правильно, я люблю копнуть немного глубже, чтобы посмотреть, на самом ли деле он полностью понимает значение этого спецификатора. В частности, я задам ему следующие вопросы:

(а) Может ли аргумент быть одновременно и const и volatile? Аргументируйте ваш ответ.

(b) Может ли указатель быть volatile? Аргументируйте ваш ответ.

(с) Что не так со следующей функцией?:

int square(volatile int *ptr)
{
   return *ptr * *ptr;
}

Ответы следующие:

(а) Да. Например, регистр состояния доступный только для чтения. Он volatile, потому что может меняться неожиданно. Он const, потому что программа не должна пытаться изменить его.

(b) Да. Хотя это не общепринятый случай. Например, когда обработчик прерываний изменяет указатель на буфер.

(с) Эта функция потенциально опасна. Назначение кода состоит в возвращении квадрата значения, указанного при помощи *ptr. Однако, поскольку *ptr указывает на volatile переменную, компилятор сгенерирует код, который выглядит примерно так:

int square(volatile int *ptr)
{
    int a,b;
    a = *ptr;
    b = *ptr;
    return a * b;
}

   Поскольку значение переменной на которую указывает ptr может неожиданно измениться, то существует возможность,  что «а» и «b» будут разными. Следовательно, этот код может возвратить число, которое не будет  квадратом! Правильный вариант кода в данном случае такой:

int square(volatile int *ptr)
{
   int a;
   a = *ptr;
   return a * a;
}

Операции с битами

9. При программировании встраиваемых систем приходится часто манипулировать битами в регистрах или переменных. Дана целая переменная «а», напишите два фрагмента кода. Первый должен установить 3-ий бит этой переменной. Второй должен очищать его. В обоих случаях, другие биты должны остаться без изменений.

Вот три наиболее распространённых ответа на этот вопрос:

(а) Никаких идей. Кандидат никоим образом не может работать со встраиваемыми системами.

(b) Используйте битовые поля. Битовые поля покинули этот мир, наряду с триграфами, как самая бестолковая часть языка Си. Битовые поля по своей природе не переносимы между компиляторами, и по существу гарантируют, что ваш код не допускает многократного использования. Недавно я имел несчастье взглянуть на драйвер, который написали в  Infineon для одного из их наиболее сложных коммуникационных чипов. Там  использовались битовые поля, и драйвер был полностью бесполезен, потому что мой компилятор выполнял битовые поля другим путём. Мораль – никогда не позволяйте программисту, не знакомому со встраиваемыми системами, подходить к реальному оборудованию.

   Я недавно смягчил свою позицию по битовым полям. По меньшей мере один производитель компиляторов (IAR) теперь предлагает ключ компилятора для определения расположения битовых полей. К тому же их компилятор генерирует оптимальный код с регистрами описанными как битовые поля, и по существу - теперь я использую битовые поля в IAR-приложениях.

(с) Используйте #define и битовые маски. Это хорошо переносимый метод и его стоит использовать. Оптимальное решение этой проблемы, на мой взгляд, было бы таким:

#define BIT3       (0?1 << 3)

static int a;

void set_bit3(void)
{
   a |= BIT3;
}

void clear_bit3(void)
{
   a &= ~BIT3;
}

   Некоторые люди, наряду с именованными константами, предпочитают задавать маску для значений set и clear. Это тоже приемлемо. Важные элементы, которые мне нужны – это использование именованных констант, наряду с конструкциями |= и &= ~

Доступ к ячейкам памяти

10. Довольно часто программистам встраиваемых систем требуется доступ к определенной ячейке памяти. В некотором проекте требуется установить целую переменную по абсолютному адресу  0?67a9 к значению 0xaa55. Напишите код, выполняющий эту задачу.

   Цель данной задачи выяснить, знаете ли Вы, что разрешено приведение целочисленных типов к указателю, чтобы получить доступ по абсолютному адресу. Правильный синтаксис может варьироваться в зависимости от стиля. Однако обычно я хочу увидеть что-то вроде этого:

int *ptr;
ptr = (int *)0?67a9;
*ptr = 0xaa55;

Более запутывающий вариант выглядит так:

*(int * const)(0?67a9) = 0xaa55;

Прерывания

11. Прерывания являются важной частью встраиваемых систем. Поэтому многие производители компиляторов предлагают к стандартному языку Си расширение для поддержки прерываний. Обычно - это новое ключевое слово __interrupt. Следующий код использует __interrupt, чтобы описать программу обработки прерываний. Прокомментируйте его.

__interrupt double compute_area(double radius)
{
   double area = PI * radius * radius;
   printf(“\nArea = %f”, area);
   return area;
}

В этой функции так много неправильного, что почти невозможно понять с чего начинать.

(а) Обработчик прерываний не может возвращать значение. Если вы не понимаете этого, то не получите работу.

(b) Обработчик прерываний не может принимать параметры. Если вы упустили это, смотрите пункт (а) для понимания ваших перспектив получить работу.

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

(d) Функция printf() тоже часто испытывает трудности с реентерабельностью и работоспособностью. Если бы вы пропустили пункты (с) и (d), то я не был бы с вами слишком строг. А если вы справились с этим двумя пунктами, то ваши перспективы получить работу выглядят всё лучше и лучше.   

Примеры кода

12. Что делает следующий фрагмент кода и почему?

void foo(void)
{
   unsigned int a = 6;
   int b = -20;
   (a+b > 6) ? puts(“> 6?) : puts(“<= 6?);
}

   Этот вопрос проверяет, разбираетесь ли вы в правилах представления целых чисел в Си, – области, которую я нахожу очень плохо понимаемой многими разработчиками. Ответ состоит в том, что выведется “> 6”. Причина -  в выражениях, включающих типы со знаком и без  все операнды приводятся к типам без знака.   Таким образом, -20 становится очень большим положительным целым, и выражение оценивается больше, чем 6. Это очень важный момент во встраиваемых  системах, где часто используются типы данных без знака. Если вы ответили на этот вопрос неправильно, то вы сильно рискуете быть не принятым на работу.

13. Прокомментируйте следующий фрагмент кода:

unsigned int zero = 0;
unsigned int compzero = 0xFFFF;       /*1’s complement of zero */

   В машинах, где int не равен 16-и битам, это будет неправильно. Следует записать так:

unsigned int compzero = ~0;

   Этот вопрос даёт возможность по-настоящему узнать, понимает ли кандидат важность длины слова в компьютере.

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

Динамическое распределение памяти

14. В чём заключаются проблемы с динамическим распределением памяти во встраиваемых системах?

   Здесь я ожидаю, что пользователь упомянет фрагментацию памяти, проблемы сбора мусора и т.д. Эта тема была хорошо описана в ESP, главным образом, Плогером. Его объяснения гораздо более проницательны, чем что-либо из того, что я могу предложить здесь, поэтому прочтите старые номера! Убаюкав кандидата ощущением ложной безопасности, я задаю такой интересный вопрос:

Что делает следующий фрагмент кода и почему?

char *ptr;
if ((ptr = (char *)malloc(0)) == NULL) {
   puts(“Got a null pointer”);
}
else {
   puts(“Got a valid pointer”);
}

   Это довольно забавная задачка. Я столкнулся с ней недавно, когда мой коллега ненароком передал значение 0 в malloc и получил назад действительный указатель! Покопавшись немного, я обнаружил, что результат функции malloc (0) определён реализацией, так что правильный ответ гласит «по обстоятельствам». Я использую этот вопрос, чтобы начать беседу о том, что, по мнению интервьюируемого, является верным для действий функции malloc. Получение правильного ответа здесь не важно, главное как вы подойдёте к проблеме и  логически обоснуете свое решение.

Typedef

15. Typedef часто используется в Си для объявления синонимов существующих типов данных. Также для подобных действий возможно использование препроцессора. Например, рассмотрите следующий фрагмент кода:

#define dPS  struct s *
typedef  struct s * tPS;

Цель в обоих случаях состоит в объявлении dPS и tPS как указателей на структуру «s». Какой метод предпочтительней и почему?

Это очень хитрый вопрос, и всякий, кто ответит на него правильно, получит мои  совершенно справедливые поздравления. Ответ заключается в том, что предпочтителен typedef. Рассмотрите объявления:

dPS p1,p2;
tPS p3,p4;

Из первого примера следует:

struct s * p1, p2;

что объявляет р1 как указатель на структуру и р2 как обычную структуру, что, вероятно, вовсе не то, что вы хотели. Второй пример правильно определяет р3 и р4 как указатели.

Запутывающий синтаксис

16. Си позволяет некоторые ужасные конструкции. Допустима ли эта конструкция, и если да, то что делает этот код?

int a = 5, b = 7, c;

c = a+++b;

   Предполагается, что этот вопрос будет весёлым завершением опроса, в то время как, верите, или нет, это вполне допустимый синтаксис. Вопрос в том, как компилятор его воспримет? Бедные авторы компиляторов обсуждали эту проблему и пришли к правилу «максимального перемалывания», которое гласит, что компилятор должен «откусить» такой большой (допустимый) кусок, какой только может. Следовательно, этот код будет восприниматься так:

c = a++ + b;

   Поэтому, после выполнения этого кода, a = 6, b = 7 & c = 12;

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

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

 

Comments   

# Guest 2010-09-20 06:18
забавный тестик, очень стимулирует учить и учить язык...
# Guest 2010-09-20 08:20
Спасибо. В закладки. Чтобы знать, куда учить Си =)
# Guest 2010-09-20 09:51
:-) узнал немного полезного! спасибо!
# Guest 2010-09-20 15:41
Спасибо! Уже завтра вместе с Bug будем собеседовать новичков по вашему тесту! :-)
# Pashgan 2010-09-20 16:17
Даааа...не завидую я им :-)
# Guest 2010-09-20 16:13
Интересно. Только про операции с битами не понятно. Зачем писать функции, когда можно обойтись макросами?
# Guest 2010-09-20 16:56
У Бара по этой статье был опросник - он пытался выяснить сколько народу из эмбеддеров действительно знает си. Оказалось, не так уж и много. Сейчас у него есть опросник по плюсам.
# Pashgan 2010-09-22 20:31
Да уж. В плюсах то еще больше закоулков и ужасных конструкций.
# Guest 2010-09-21 16:25
Не знаю, тесты на мой взгляд ерунда. Прохождение теста скорее западная мода, чем объективный показатель знаний и способностей, тут не оценить например творческую составляющую, а она для разработок очень важна, подобная узкая специализация делает из инженеров клерков. Инженер встроенных систем не только должен уметь программировать , но и знать электронику как цифровую так и аналоговую и всё это уметь связать, а не просто знать, что такое int*const a. Лучше уж пусть притаскивает на собеседование своё изобретение и рассказывает как чего писал и делал или непосредственно в работе испытать. Советская инженерная школа была самая лучшая, а сейчас одни клерки выпускаются.
# САБ 2010-09-21 18:03
Выпускник советской инженерной школы (если его учили на программиста) ответы на эти вопросы должен знать как "Отче наш". Никто не утверждал, что этот тест - единственный критерий. Он только начало собеседования, позволяющее не тратить время на совсем безнадежных кандидатов.
# Guest 2010-09-22 06:29
А можно узнать источник?
# Pashgan 2010-09-22 20:28
"A ‘C’ Test: The 0?10 Best Questions for Would-be Embedded Programmers" Nigel Jones.
Где впервые была опубликована не знаю. Висит на нескольких сайтах.
# Iron Bug 2010-10-13 04:45
Забавно. По-моему, ничего такого, чего не было написано ещё у Каргени и Ритчи. И что, сейчас программисты не знают основ Си? Правда, мне тут недавно сказали, что "системные программисты уже не нужны" :) Вот так вот. Юзер на шарпе уверен в своей абсолютной крутизне: у него виджеты с картинками. А системный программист... что он? Ковыряется, ковыряется, а результата даже не видно на экране :)
# Pashgan 2010-10-13 20:39
Ну, допустим, у Кернигана и Ритчи ничего нет про прерывания и использование volatile (в том ключе, каком он используется при программировани и микроконтроллер ов)
# Alex_cnc 2010-10-23 15:23
Pashgan, я в сфере программировани я микроконтроллер ов новачек, собираю информацию в Инете по крохам, ваши статьи как бальзам на израненную поверхностными изложениями душу - доступно, лаконично и достаточно глубоко. Был бы очень благодарен, если бы в одной из статей вы описали работу с картами SD/MMC через SPI.
# Pashgan 2010-10-25 18:31
Это хорошо. Значит какой то толк от этой работы есть. А по-поводу карт не знаю. Руки не доходят.
# gazlan 2010-10-26 12:33
Тесты простенькие (хотя на С уже лет 10 не программирую). А вот орфографию перевода поправить не помешало бы. И еще: "оптимально" не бывает ни более, ни менее, ни каким-либо еще. Это "булева" переменная :-)
# Zebra 2011-02-18 15:37
Оптимально само по себе тоже не бывает ;)
Бывает оптимально в смысле какого-то критерия оптимальности.

И подоптимально.
# A-10 2010-11-08 21:58
Очень интересно. А не подскажите-ли какую-нибудь удачную лит-ру по программировани ю на Си именно для встроенных систем?
# Pashgan 2010-11-09 23:43
Мне попадалась только одна переводная книга - "Встраиваемые системы. Проектирование приложений на микроконтроллер ах семейства 68HC12/HCS12 с применением языка С". Она про мотороловские микроконтроллер ы, но я ее все равно купил. Там есть краткое описание языка С, примеры сопряжения мк с различными устройствами, описание различных проектов, рекомендации для разработчиков, введение в осрв. В целом неплохая книга.
Зарубежной литературы по этой теме гораздо больше.
# noonv 2010-12-18 11:01
замечательный тест!
# void 2011-01-20 19:32
Я ужаснулся своим плохим знаниям, сажусь учить основы СИ
# Pashgan 2011-01-22 23:18
Стефан Кочан "Программирован ие на Си", Керниган и Ритчи (это классика, но мне больше первая книга нравится), Семюэл П.Харбисон "Язык программировани я Си" (это справочник по стандарту Си) - тебе и всем нам в помощь ;-)
# Zebra 2011-02-18 15:39
Си учил по Трою.
Ответил на все вопросы верно, за исключением 13-го. Разве stdtypes отменили, и юзать типы с известной длиной нельзя ?
# Saycheese 2011-02-05 22:27
"Ключевое слово volatile информирует компилятор о том, что переменная может быть изменена не только из текущего выполняемого кода, но и из других мест. Тогда компилятор будет избегать определенных оптимизаций этой переменной. " - ну и лоховство! Тоже мне, собеседователь.
# Pashgan 2011-02-08 20:09
Quote:
ну и лоховство! Тоже мне, собеседователь
Обосновывай свое мнение. Хохмить и оставлять пустые комментарии может каждый.
# MyQuake 2011-03-15 21:42
http://alenacpp.blogspot.com/2006/04/volatile.html You can read about this at that caucasian-USA_A llah - C++ - akbar girl's blog.
# Zebra 2011-02-18 15:44
У меня общий вопрос, автору теста.
Если вы такой поклонник макросов (макрух), отчего не дбавили вопросы про дупль диезы (##). На мой взгляд это достаточно редкий случай, когда макрухи имеют право на жисть.
# Pashgan 2011-02-19 21:27
Автор теста, Nigel Jones, не бывает на ChipEnable. Вряд ли он тебе ответит.
# Zebra 2011-03-12 08:48
Хорошо, у меня вопрос к Вам.
Как Вы сами относитесь к макросам.
# Pashgan 2011-03-15 19:46
Использую с осторожностью
# Секох 2011-04-02 06:37
Интересно, что предполагается сделать экзаменатору, если ответ будет такой: я не знаю и не хочу знать, что делает конкретно данный код (можно посмотреть в справочнике, если очень нужно) - я понимаю, какого эффекта здесь пытаются добиться - код, который дает нужный эффект, выглядит так: ... - и далее максимально консервативный вариант, который заведомо на 100% работоспособен и переносим?
Или, если тестируемый отметит, что стиль программировани я, который используется в доброй половине примеров - кроме очень редких случаев, когда сокращение действительно необходимо, вроде int *a[10]; - это пример плохого стиля, недоопределений , итп, и писать так программу - недопустимо само по себе?
/* Чтобы пояснить сказанное выше, представим себе вопрос теста - что делает следующий код?
int p=1; printf("%d %d %d\n", p++, p++, p++);
Правильный ответ - да ничего хорошего он не делает, написать так - ошибка, причем школьная. Будет ли принят данный ответ автором текста? :)
А как насчет такого (a > b | c) ? Если мне такое действительно будет нужно, то я скорее напишу ((a>b)|c), чтобы не думать. Или (a>(b|c)), в зависимости от того, что реально имелось в виду. Вариант, "чтобы не дразнить гусей", вроде (((a>b)?1:0)|c) , тоже абсолютно законный, т.к. компилятор при генерации кода все равно делает именно это. */
# Elephant 2011-05-29 22:04
Даже интересно, насчет:
int p=1;
printf("%d %d %d\n", p++, p++, p++);
Может кто-нибудь объяснить природу ошибки, поскольку все написанное в рамках дозволенного.
# Секох 2011-04-02 06:43
Обработчик прерываний, печатающий в терминал и возвращающий значение... Если бы там был interrupt int proc(int a){} то в зависимости от компилятора оно вполне могло бы и компилироваться , и выполняться. Конечно, никаких параметров процедура ниоткуда не получает и никуда не передает. В а получаем мусор, возвращаемое значение пропадает в никуда. Вот и весь вопрос ценой в получение трудовой занятости. :) Вопрос про double немножко шире. Дело в том, что мало того, вещественное число - тип для компьютера не родной, так еще и тип double может быть не родным для данной конкретной платформы. Мне довелось работать с компилятором, который понимал, что у него есть double, но "не знал", что у него есть FPU, у которого есть регистры и стек. Процедуры обработки прерываний, естественно, с плавухой корректно работать не могли.
# Секох 2011-04-02 06:44
Лично у меня возник ряд других вопросов по поводу интерпретации автором теста примера с его обработчиком. Если он такой пурист, почему он не упомянул о возможности получения sNaN в аргументе вместе с мусором (как следствие, аппаратный сбой)? Почему не упомянул, что внутри printf() вообще-то бывает вызов malloc(), что в контексте означает приглашение для всех видов несчастий? И кстати, если мы говорим про встраиваемые микроконтроллер ы, *куда именно* будет выводиться печать? в COM-порт с его собственными прерываниями, буфером обмена, циклами ожидания, и возможностью получить XOFF? :) Что делать, наконец, если это действие - дождаться аппаратного прерывания, вынуть откуда-то параметры, что-то вычислить, и куда-то напечатать - все-таки нужно? А что делать, если локально, на короткий период времени, частота прерываний может превышать скорость печати? скорость обработки? :)
Вот последние два вопроса (или три, в зависимости от того, как считать) - в отличие от ряда других вопросов - потенциальному аппликанту я бы задал. Они требуют не столько книжного знания, которое можно прочитать, сколько опыта работы и понимания, что и для чего делается в программировани и вообще.
# Miha 2011-04-07 02:20
абсолютно согласен с Секох!
некорректными вопросами на собеседовании можно запутать кого угодно! Придумать запутанный вопрос - плёвое дело, но насколько этот вопрос действительно соответствует реалиям?
Для ембеддеров важнО не столько знание синтаксиса, сколько понимание того, что происходит в реальной железке с её ограниченными возможностями, а для реализации концепции, сформировавшейс я на основе знаний железа, всегда можно заглянуть в шпаргалку!
кстати, встречал утверждение, что способность работать с литературой, способность найти нужную информацию как раз и является произнаком профессионализм а! так я с этис утверждением абсолютно согласен!
и ещё: автор считает себя крутым "сишником", и не стесняется оценивать способности кандидатов, но что-то он ассемблер не упоминает. Или он отрицает его использование во встраиваемых устройствах?
# rockpazanunahuy 2011-04-18 17:55
сейчас прохожу курсы по embedded, лично мне вопросы показались достаточно тривиальными :)
# faza-maza 2011-04-26 06:53
Действительно, большинство вопросов связаны с желанием автора теста загнать "жертву" в угол с помощью запутанных вопросов, граничащих (по степени реализации кода) с дилетантским стилем программировани я. Нормальный кандидат ответил бы сразу: если мне будет нужно разобраться, что делает этот код, то проще переписать его "по-человечески " и не заморачивать себя и других, которые придут после меня.
ЗЫ: программирую не то чтобы чистые ембеды, но тоже не десктопы, больше 10 лет.
# Никита 2011-05-04 14:27
Quote:
3. Каково назначение директивы препроцессора #error?

Этот вопрос очень полезен, если требуется отличить нормальных парней от ботаников. Обычно только ботаники читают приложения к руководствам по Си, чтобы узнать о таких вещах.
чушь собачья! необязательно быть ботаником и знать об этой директиве.
к тому же чем больше знаешь - тем лучше в любом случае.
# INT1 2011-05-22 03:38
Quote:
Поэтому, после выполнения этого кода, a = 6, b = 7 & c = 12;
question
11.Вы уверены, что 6+7=12?
12.Что сделает компилятор увидев такую конструкцию Quote:
b = 7 & c = 12
? :-)
# INT1 2011-05-22 03:52
PS. 11,12 следует читать 0х11,0х12.
# BorisSpb 2012-01-13 08:28
К a применяется пост-инкремент, поэтому сначала c = a + b, т.е. 5 + 7 = 12, а потом a++. А строка b = 7 & c = 12 - это не часть кода.
# Uncle 2011-05-29 22:05
Полный отстой.
Я в профессии уже 10 лет из них разработчиком embedded 8. Скажу прямо, статья крайне субъективна. Во-первых, относительно стандарта «С», сразу скажу - это для теоретиков. Многие вещи, описанные в стандарте, просто не возможно реализовать на 8-ми, 16-ти или иногда даже 32-ух битных контроллерах, или, если реализуешь, то пользы будет как от выеденного яйца. Это связано с многими аспектами о которых долго можно говорить, кто не верит, может для начала просто сравнить карту памяти для разных ядер микроконтроллер ов. Ну а кому не понятно какое имеет значение карты памяти на язы, то боюсь придеться еще поработать в профессии, чтение книг здесь может не помочь. Многие полезные фичи ядра вообще не возможно расскрыть не перелопатив этот стандарт и не создав свой стандарт в стандарте. Кто хочет пример, привожу, компилятор «Keil», «Iar». В компиляторе «Keil» по умолчанию нельзя сделать рекурсию. Что об этом скажет стандарт, многоуважаемые читатели такового? Таких примеров вагон и телега. Даже для одного ядра язык двух приведенных выше компиляторов настолько разный, что об переносимости кода и речи быть не может. Это часть статьи вообще меня порадовала, типа биты это зло! Однако, похоже, где-то все-таки сознание сработало, «Iar» натолкнул на мысли.
# Uncle 2011-05-29 22:06
Далее, прекрасный момент про for( ; ; ). Как там, Керниган и Ритчи? Меня порадует, если соискатель скажет что-то вроде: «wile - реализуется всего парой команд процессора, в отличии от громоздкого for. Вот наверно после этого я пойму, что человек не просто код писал, но еще и в суть вещей заглядывал и понимает откуда ноги растут.
Что там еще из самого, а ну да установить третий бит. Ну вообще на этот вопрос мало кто не ответил, за мою практику, тем не менее, это не показатель. Был бы мозг!!! А булева алгебра учиться за день. У меня студент был в подчинении, он вообще «С» не знал. Я ему всего три лекции по часу прочитал и на телефоне постоянно с ним был, наверно пол недели, и в течении следующего месяца он мне сдал проект без нареканий. Кто-нибудь из вас видел, что бы язык учили за полнедели? Вообще, ребята скажу так, человека способного выполнить работу в срок и хорошо видно сразу, но не каждый теоретик может его заметить. Я могу скастить большие пробелы в знаниях соискателю, все-таки, как я уже заметил, нефиг быть такими узколобыми и тупа прикрывать свой зад словами «это стандарт». Я практик, не теоретик, мне этот стандарт хорош, только если он напечатан на мягкой бумаге. Надо понять чего стоит сам человек. Уровень интеллекта и желание капать - вот характеристики плодотворной почвы в которую стоит заложить зерно и не сожалеть потом о том, что потратил время. Да вот еще, о «volatile» походу автор долго врубался нах. это вообще надо и как оно работает, потому, наверно и не знает, как покруче зарисоватьсятьс я этим своим приобретенным знанием. Хотя вроде врубился без нареканий, описал синдромы правильно, правда насчет работоспособнос ти примера с функцией можно поспорить. Дело в том, что компиляторы обычно не настолько тупы и преобразуют не к виду: a = *ptr; b = *ptr;
а к виду a = b = *ptr; что в корень меняет дело. Ну да ладно, в целом согласен, порой лучше перебздеть, чем не добздеть и автор это нам демонстрирует.
# BorisSpb 2012-01-13 08:38
Вообще-то for(;;) реализуется одной командой процессора наподобие goto или jmp по адресу начала цикла и все!
# Uncle 2011-05-29 22:07
Однако:
Тот кто не писал под embedded наверняка не будет знать volatile, и это не говорит ровным счетом не о чем, кроме того, что он либо, как я уже говорил, не писал на embedded, либо писал на таком уровне, до которого автору еще похоже приодеться подрасти. Это к примеру «TreadX» или «Android». Мало кто догадывается, особенно те кто на нем пишет, но «Android» - это тоже embedded. Начнем с того что попроще, «ThreadX». В этой операционке до регистра придется капать так далеко, что свет покажется не мил, да и собственно зачем, если есть масса функций, которые и значение заберут и преобразуют и mutex поставят и semaphore прикрутить не забудут и event тебе дернут, если ты уже совсем ленивый.
# Uncle 2011-05-29 22:08
Резюме: вы, конечно, можете поспрашивать все вышеизложенное у соискателя, наверно я бы тоже что-то из этого спросил, но скажу однозначно, ваш соискатель, не зная ответа на половину этих вопросов, вполне возможно уже замутил такие штуки, которые вы и повторить не решитесь. А не знание вот всего вот этого, может быть просто объяснено иной спецификой решаемых задач (к примеру, пока вы подумываете, не прикрутить ли вам ОС к процу, у него уже несколько проектов на микро линукс и видел он ваши volatile, там, где вы еще не скоро увидите мьтексы). Вот, как-то так!
# Uncle 2011-05-29 23:10
Кажется, мой предыдущий комент оказался немного порванны, ну что же, придется повторить:

Я было закончил свои коменты, но душа поэта не сдержалась, упал глаз еще на пару узколобостей автора:
Code:
void foo(void)
{
unsigned int a = 6;
int b = -20;
(a+b > 6) ? puts(“> 6?) : puts(“<=?);
}


Не правильный ответ на данный вопрос может быть не по причине на знания, цетирую: "правил представления целых чисел", но так же из-за вопроса, на который мало кто ответит однозначна: к какому виду (знаковому или безнаковому) предпочтет привезти ответ компилятор. Сказать по правде, человек, который однозначна на это ответит меня насторожит. На практике достаточно просто привести тип, и это правильно. Тем самым разработчик экономит массу времени своего на отладке (если он всетаки ошибся) и моего на рефакторинке и чеккоде. Более того, если он этого не сделает, я попрошу его это сделать, или исправлю за него. Тем самым я создаю более надежный код, который с большей вероятностью заработает на другом компиляторе, в котором, к примеру, по каким-то причинам разработчики не дочитали стандарт и лоханулись, или наоборот, лоханулись на предыдущей версии компилятора, а в новой все исправили и после обновления код стал не рабочим. Вот это все далеко не редкость.
# внимательный 2011-06-16 16:52
Uncle, вы крайне невнимательны.
Nigel Jones плевать хотел на Вас. =)
# Алекс 2014-09-22 15:47
Отличный комментарий! Сам недавно начал изучать программировани е. Предпочитаю глубоко вникнуть и понять, а потом уже применять творчески полученные знания на практике. Чем больше изучаю, тем больше понимаю, что сейчас главенствует какая-то узколобость и нежелание разобраться в сути... Чувствуется, Вы человек практичный и понимающий. И мне кажется, что под Вашим, таким чутким руководством, я также за пол недели освоил Си программировани е.
# Pashgan 2011-09-21 20:58
Uncle, спасибо за такой развернутый комментарий. Вы могли бы целую статью на эту тему написать.
# Wukrlvy 2012-01-31 18:15
Я не так критичен, как #Unde. Статья посвящена тем, кто программирует на СИ. И если пользователь работает на "Android", так зачем ему это?
В целом, мне статья очень понравилась. Ошибки? Оно, конечно, есть. Но, кто не ошибается, тот ничего не делает. На СИ смотрю, как на Закон, а на компилятор, как на Исполнительную власть. И то, и другое воспринимаю, как есть. Ну и, не знание закона не освобожгвет от ответственности при его несоблюдении.
Для меня - это первый сайт, который я выловил в Инете по СИ для IAR на русском. И примеры достаточно навороченные, а не просто типа "дернуть ножку". Хотя реально все, конечно, значительно сложней.
Все это читал на английском в фирменных материалах и, конечно, многое не допонял.
Подавляющее большинство пользователей делают простые проекты. Для них информации вполне достаточно. У меня же еще остались вопросы и, если тема не закрыта, буду пробовать их задавать.
А тестовые вопросы - оно, конечно, приятно, какие бы они там не были!
# Тимофей 2015-05-21 05:54
int *ptr;
ptr = (int *)0?67a9;
*ptr = 0xaa55;

Более запутывающий вариант выглядит так:

*(int * const)(0?67a9) = 0xaa55;

ОНО НЕ КОМПИЛИРУЕТСЯ В ИАРЕ даже, не говоря уже о том, работает ли.
# Тимофей 2015-05-21 05:54
int *ptr;
ptr = (int *)0?67a9;
*ptr = 0xaa55;

Более запутывающий вариант выглядит так:

*(int * const)(0?67a9) = 0xaa55;
ОНО НЕ КОМПИЛИРУЕТСЯ В ИАРЕ.

У вас недостаточно прав для комментирования.