Какие важные понятия в Си, которые вы не узнали от своих учителей? [закрыто]

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

В этом году они станут вторыми год обучения C (они должны знать, что такое указатель и как его использовать, но, конечно, это понятие еще не усвоено)

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

  • Разработать алгоритм (и написать его в псевдокоде) до кодировать его в C (подумайте раньше) coding)
  • Сделайте ваш код читабельным (комментарии, имена переменных, ...) и
  • Указатели, указатели, указатели! (что это такое, как и когда его использовать, распределение памяти и т. д ...)

Согласно вашему опыту, каковы наиболее важные понятия в Си, которым ваши учителя никогда не учили ты? На каком конкретном моменте я должен сосредоточиться?

Например, должен ли я познакомить их с некоторыми инструментами (lint, ...)?

25
задан 6 revs, 4 users 57% 6 September 2017 в 17:22
поделиться

55 ответов

Массив - это указатель. Различия между * (разыменование) и & amp; (addressof) операторов и когда использовать оба

И подчеркивают, что лучшее (и действительно единственное) реальное место для C в наши дни - это встроенные системы и приложения реального времени, где ресурсы ограничены, а время выполнения является фактор.

Я не очень ценил C как язык, пока не взял класс встроенных микропроцессорных систем, и мы реализовали аппаратное обеспечение, прочитав руководство программиста в руководстве к плате Motorolla Dragonball. Следовательно, если это вообще возможно (что может быть сложно, так как вам понадобится дешевое оборудование), попытайтесь заставить их работать над аналогичными проектами (реализация таблиц UART и векторов прерываний и т. Д.) ...

Поскольку такие вещи, как обработка строк, сортировка и т. Д., Являются игрушечными задачами классической школы, они на самом деле уже не так полезны и расстраивают учащихся, которые знают, что существуют более простые способы. Это намного более полезно для & amp; байт с битовой маской и смотреть, как загорается светодиод.

О, и я никогда не узнал о том, как использовать такие вещи, как gcc в школе, или что на самом деле происходит с make-файлами. Прагматичные программисты говорят, что это полезно знать.

1
ответ дан moo 6 September 2017 в 17:22
поделиться

Я использовал C89 во встроенном программировании, и отладка аппаратного обеспечения была кошмарной. У нас было несколько соглашений о кодировании, которые спасли наши здравомыслия:

  1. Все функции возвращают уникальный код ошибки.
  2. Все возвращаемые значения являются автоматическими переменными, передаваемыми по ссылке.

Например:

#define NOERR 0
#define VariableLookupNULL 1024
#define VariableLookupNOTFOUND 1025
... separate #define for each error
#define EvaluateExpressionNULL 1055
#define EvaluateExpressionUNKNOWNOP 1056


int EvaluateExpression( char *expression, int* result )
{
    ASSERT(result != 0);
    if (expression==0)
        return EvaluateExpressionNULL;

    *result = 0;
    while (*expression != 0)
    {
        switch (*expression)
        {
            case ' ':
            case '\t':
                break;  // ignore whitespace

            case 'a':
            ... other variables
            {
                int var = 0;
                int lookupResult = VariableLookup(*expression, &var);
                if (lookupResult != NOERR)
                    return lookupResult;

                *result += var;
                break;
            }

            ... check operators, et al.

            default:
                return EvaluateExpressionUNKNOWNOP;
        }

        ++expression;
    }

    return NOERR;
}

ASSERT был макросом отладки, который прерывал время выполнения.

1
ответ дан 2 revs 6 September 2017 в 17:22
поделиться

Хотя я и не был связан напрямую с C, я хотел бы узнать о технике использования ASSERT для раннего обнаружения ошибок (например, задолго до некоторой странной ошибки, вызванной перезаписью памяти). Вместо этого я самостоятельно обнаружил это несколько лет спустя. Эта техника обнаружила множество ошибок (в том числе очень тонких), которые в противном случае остались бы незамеченными.

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

Например:

assert(pInt)

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

Или

assert(pInt < pMax)

, где pMax указывает сразу за концом целочисленного массива, над которым работает pInt.

Или

утверждают (yMass> 57.90)

(где yMass - масса одиночного заряженного y-иона для пептида)

1
ответ дан Peter Mortensen 6 September 2017 в 17:22
поделиться

Хотелось бы, чтобы мои профессора научили нас, как пользоваться отладчиком. Вместо этого я возился с инструментарием своего кода, пытаясь выяснить проблемы с помощью printf. Обнаружение GDB было похоже на включение лампочки. Возможность отладки сбоя с помощью дампа ядра была особенно полезна, поскольку многие новые ошибки программирования на C обычно возникают из-за плохой логики указателей.

В настоящее время юнит-тестирование, вероятно, будет хорошей практикой для обучения.

1
ответ дан Ted Elliott 6 September 2017 в 17:22
поделиться

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

Это будет работать, как и предполагалось, в большинстве 32-битных систем:

int * pInt = (int *) 0xDEADBEEF

Я не уверен, что будет хорошим значением в 64-битной системе .

0
ответ дан Peter Mortensen 6 September 2017 в 17:22
поделиться

Простой инструмент отладки, printf (). Если у вас нет инструментов отладки !!

0
ответ дан Sachin 6 September 2017 в 17:22
поделиться

Одна точка с запятой является операцией NOP:

if(cond0) { /*...*/ }
else if(cond1) ;  //is correct and does nothing 
else { /*...*/} 

Оператор запятой:

a = (++i, k);  //eq: ++i; a = k;
0
ответ дан saxi 6 September 2017 в 17:22
поделиться

Ориентация объекта:

struct Class {
    size_t size;
    void * (* ctor) (void * self, va_list * app); // constructor method
    void * (* dtor) (void * self);                // destructor method
    void (* draw) (const void * self);            // draw method
};

( Источник кода )

11
ответ дан Paul Biggar 6 September 2017 в 17:22
поделиться

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

Также напомним, что C - это не C ++, Java, C # и т. Д. - хорошая идея. Чаще всего это происходит, когда вы видите, что кто-то рассматривает char * как строку в C ++.

13
ответ дан Jon 6 September 2017 в 17:22
поделиться

Всегда активные предупреждения. С GCC используйте как минимум -Wall -Wextra -Wstrict-prototypes -Wwrite-strings.

Ввод / вывод сложен. scanf() это зло. gets() не следует никогда использовать.

Когда вы печатаете что-то, что не определено '\n', вы должны сбросить stdout, если вы хотите напечатать это немедленно, например

printf("Type something: ");
fflush(stdout);
getchar();

По возможности используйте указатели const. Например. void foo(const char* p);.

Используйте size_t для хранения размеров.

Литеральные струны, как правило, не могут быть изменены, поэтому сделайте их const. Например. const char* p = "whatever";.

8
ответ дан Bastien Léonard 6 September 2017 в 17:22
поделиться
  1. Проверьте границы
  2. Проверьте границы,

    и, конечно,

  3. Проверьте границы.

И если вы забыли одно из этих правил, используйте Valgrind. Это относится к массивам, строкам и указателям, но на самом деле очень легко забыть о том, что вы действительно делаете, когда делаете выделения и арифметику памяти.

3
ответ дан juanjux 6 September 2017 в 17:22
поделиться

Учитывая их опыт, возможно, хороший фокус на C для встроенных систем, включая:

  • Инструменты статического анализа (например, PC-Lint )
  • МИСР-С .
  • Воздействие нескольких процессоров (например, PIC, STM32) и компиляторов
  • Как отлаживать.
  • Проблемы в реальном времени, включая прерывания, устранение неполадок, простое планирование / ОСРВ.
  • Разработка программного обеспечения.

И очень существенно: ПО для управления версиями . Я работаю в промышленности и использую это религиозно, но я поражен тем, что это никогда не упоминалось в моей степени!

3
ответ дан Steve Melnikoff 6 September 2017 в 17:22
поделиться

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

Обучайте их указателям, но учите их хорошо, говоря им, что они являются целочисленной переменной, представляющей позицию в памяти (в большинстве курсов они также проходят определенную подготовку по сборке, даже если это некая воображаемая машина, поэтому они должны быть способны понять это), а не переменная с префиксом звездочки, которая каким-то образом указывает на что-то и иногда становится массивом (C не Java). Научите их, что массивы C - это просто указатель + индекс.

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

Стандартной библиотекой является также C, пусть они используют ее, и их программы мучительно умирают в ваших частных тестах из-за использования get () и strcpy () или двойного освобождения чего-либо.

Заставьте их работать с переменными различного типа, порядком байтов (ваши тесты могут выполняться в другой арке), преобразованием с плавающей точкой в ​​int. Заставьте их использовать маски и побитовые операторы.

то есть. научить их C.

Вместо этого я получил некоторую пакетную обработку в C, которая также могла бы быть выполнена в GW-BASIC.

5
ответ дан jbcreix 6 September 2017 в 17:22
поделиться
1113 Слишком много, чтобы назвать их всех. Некоторые из них специфичны для C; некоторые из них являются общими вещами наилучшей практики.

  • Научитесь использовать доступные инструменты
    • Система контроля версий. Каждый раз, когда это работает, проверяйте это.
    • Инструменты Diff: diff, rdiff, meld, kdiff3 и т. Д. Особенно в сочетании с RCS.
    • Опции компилятора. -Wextra -Wall __attribute __ ((выровненный (8))), как упаковать структуры.
    • make: Создание отладочной и рабочей версий.
    • Отладчик: Как получить и интерпретировать трассировку стека. Как установить точки останова. Как перешагнуть / перебрать код.
    • Редактор: компиляция в редакторе. Откройте несколько окон, M-x tags-query-replace (мои корни emacs отображаются?) И т. Д.
    • cscope, kscope, [ce] тэги или другие инструменты просмотра источников
  • Программировать в обороне. assert (foo! = NULL) в -DDEBUG; очистить пользовательский ввод.
  • Остановка и загорание при обнаружении ошибки. Отладка становится проще, когда вы дампите две строки после обнаружения проблемы.
  • Поддерживать компиляцию с 0 предупреждениями с включенными -Wextra и -Wall.
  • Не кладите все в 1 огромный .c файл.
  • Тест. Тестовое задание. И проверить еще. И проверьте эти тесты рядом с вашим источником. Потому что инструктор может вернуться и изменить требования после того, как он был сдан один раз.
2
ответ дан user47559 6 September 2017 в 17:22
поделиться

Научите их юнит-тестированию.

5
ответ дан Bryan Oakley 6 September 2017 в 17:22
поделиться

Гигиенические имена в макросах C:

#define SOME_MACRO(_x) do {     \
  int *x = (_x);                \
  ...                           \
} while(0)

Определение x таким образом внутри макроса опасно, поскольку (_x) может также расширяться до x, заканчиваясь на:

do {
  int *x = x;
  ...
} while(0)

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

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

0
ответ дан Peaker 6 September 2017 в 17:22
поделиться

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

Например, концепция арифметики указателей была для меня чужой, пока я уже несколько лет не использовал C ++:

Примеры:

  • c [0] эквивалентно * c
  • c [1] эквивалентно * (c + 1)
  • Итерация цикла: for (char * c = str; * c! = '\ 0'; c ++)
  • и т. Д. ...

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

РЕДАКТИРОВАТЬ: Как я обратил на себя внимание комментарием, который я только что прочитал на другой ответ, я думаю, что есть также некоторая ценность в обсуждении тонких различий между указателями и массивами (и как поставить два вместе, чтобы облегчить некоторые довольно сложные структуры), а также о том, как правильно использовать ключевое слово const по отношению к объявлениям указателей.

32
ответ дан 2 revs 6 September 2017 в 17:22
поделиться

(Опасные) побочные эффекты макросов.

10
ответ дан freespace 6 September 2017 в 17:22
поделиться

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

Прежде всего, нужно точно знать, что такое ошибка сегментации, а также как с ними справиться.

Знание того, как использовать GDB, прекрасно. Знание того, как использовать валгринд, прекрасно.

Разработка стиля программирования на C ... Например, я склонен писать достаточно объектно-ориентированный код, когда пишу большие программы на C (обычно все функции в конкретном файле .C принимают некоторую (1) конкретную структуру * и работают на этом ... у меня, как правило, есть foo * foo_create () и foo_destroy (foo *) ctor's и dtors ...) ...

8
ответ дан dicroce 6 September 2017 в 17:22
поделиться

Важным понятием в Си, которое я не изучил у моих учителей, является:

Оператор * не означает «указатель на» (слева). Вместо этого он является оператором разыменования - точно так же, как и в правой части (да, я знаю, что это беспокоит некоторых).

Таким образом:

int *pInt

означает, что когда разыменовывается pInt, вы получаете int. Таким образом, pInt является указателем на int. Или по-другому: * pInt - это int - разыменованный pInt - это int; В этом случае pInt должен быть указателем на int (иначе мы не получили бы int, когда он разыменовывался).

Это означает, что нет необходимости изучать более сложные объявления наизусть:

const char *pChar

* pChar имеет тип const char. Таким образом, pChar является указателем на const char.


char *const pChar

* const pChar имеет тип char. Таким образом, const pChar является указателем на char (сам pChar является константой).


const char * const pChar

* const pChar имеет тип const char. Таким образом, const pChar является указателем на const char (сам pChar является константой).

2
ответ дан Peter Mortensen 6 September 2017 в 17:22
поделиться

Правила целочисленных рекламных акций; представление пустых указателей; выравнивание; последовательности точек; какие-то интересные оптимизации, которые разрешено делать компилятору; что не определено, не определено и определена реализация - и что это значит. Хорошие практики также важны, и, к сожалению, некоторые профессиональные руководящие принципы кодирования содержат некоторые действительно глупые вещи. Например: сделайте if (foo) free(foo); вместо free(foo);, когда foo может быть NULL, в то время как правильный совет будет совершенно противоположным: делайте free(foo) и никогда if (foo) free(foo); Я также официально устал от дерьмового многопоточный код, поэтому, пожалуйста, сообщите своим студентам, как правильно писать многопоточные программы (предоставив им подмножество известных и доказуемо безопасных методов и запретив им использовать что-либо еще или придумав что-то самостоятельно), или предупредите их, что это слишком сложно для них , Также скажите им, что переполнение буфера неприемлемо ни в одном контексте - и переполнение стека не допускается;)

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

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

Прежде чем я забуду: скажите им, чтобы они всегда компилировались с как минимум -Wall -Wextra -Werror (Я склоняюсь к добавлению -Wuninitialized -Winit-self -Wswitch-enum -Wstrict-aliasing -Wundef -Wshadow -Wpointer-arith -Wbad-function-cast -Wcast-qual -Wcast-align -Wwrite-strings -Wstrict-prototypes -Wmissing-прототипы -Wmissing-объявления -Wold-style-definition -Wredundant-decls)

1
ответ дан xilun 6 September 2017 в 17:22
поделиться

Инструменты важны, поэтому я рекомендую хотя бы кое-что упомянуть о

  • Make-файлах и о том, как работает процесс сборки
  • gdb
  • lint
  • полезность предупреждений компилятора

Что касается C, я думаю, что важно подчеркнуть, что программист должен знать, что на самом деле означает «неопределенное поведение», то есть знать, что в будущем может возникнуть проблема даже если кажется, что он работает с текущей комбинацией компилятор / платформа.

Редактировать: я забыл: научить их искать и задавать правильные вопросы о SO!

9
ответ дан groovingandi 6 September 2017 в 17:22
поделиться

Как загрузить ленту в магнитофон - я не шучу, я изучил C на ZX Spectrum, и каждая компиляция требовала загрузки компилятора с ленты.

Это были времена: D

0
ответ дан zbychuk 6 September 2017 в 17:22
поделиться

Использование ключевого слова const в контексте указателей:

Разница между следующими объявлениями:

 A)   const char* pChar  // pointer to a CONSTANT char
 B)   char* const pChar  // CONSTANT pointer to a char
 C)   const char* const pChar  // Both

То есть с A:

const char* pChar = 'M';
*pChar = 'S'; // error: you can't modify value pointed by pChar

И с B:

char OneChar = 'M';
char AnotherChar = 'S';
char* const pChar = &OneChar;
pChar = &AnotherChar; // error: you can't modify address of pChar
34
ответ дан 28 November 2019 в 17:33
поделиться

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

2
ответ дан 28 November 2019 в 17:33
поделиться

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

1) Valgrind - отличный инструмент. Он феноменально прост в использовании и отлично отслеживает утечки и повреждение памяти.

Это поможет им понять модель памяти C: что это такое, что вы можете и чего не должны делать.

2 ) GDB + Emacs с gdb-many-windows. Или, на самом деле, любой другой интегрированный отладчик.

Это поможет тем, кому лень проходить код карандашом и бумагой.


На самом деле не ограничивается C; вот что, я думаю, им следует научиться:

1) Как правильно писать код: Как писать неподдерживаемый код . Читая это, я обнаружил как минимум три преступления, в которых был виновен.

Серьезно, мы пишем код для других программистов . Таким образом, для нас важнее писать ясно , чем писать умно .

Вы говорите, что ваши ученики на самом деле не программисты (они инженеры). Итак, они не должны делать хитрых вещей, они должны сосредоточиться на четком кодировании .

2) STFW. Когда я начал программировать (я начал на Паскале, затем перешел на C), я делал это, читая книги. Я провел бесчисленное количество часов, пытаясь понять, как что-то делать.

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

] Ваши ученики инженеры; у них не так много времени посвящать программированию . Итак, немного времени у них есть, им следует потратить на чтение чужого кода и, возможно, на освежение идиом .


В общем, Си довольно простой язык для изучения. У них будет гораздо больше проблем с написанием чего-либо длиннее нескольких строк, чем у них будет усвоение независимых понятий.

19
ответ дан 28 November 2019 в 17:33
поделиться

беззнаковое и подписанное.

Операторы битового сдвига

Битовая маскировка

Битовая установка

целочисленные размеры (8 бит, 16 бит, 32 бит)

13
ответ дан 28 November 2019 в 17:33
поделиться

Портативность - редко изучается или упоминается в школе, но часто встречается в реальном мире.

11
ответ дан 28 November 2019 в 17:33
поделиться

Используйте valgrind

9
ответ дан 28 November 2019 в 17:33
поделиться

Используйте последовательный и читаемый стиль кодирования.

(Это должно помочь вам при проверке их код тоже.)

По теме: Не оптимизируйте преждевременно. Сначала выполните профиль, чтобы увидеть узкое место.

8
ответ дан 28 November 2019 в 17:33
поделиться
Другие вопросы по тегам:

Похожие вопросы: