Cc++ — Помогите поправить код


Содержание

Почему этот код генерирует предупреждения о переполнении буфера (C6385 / C6386) при анализе кода в Visual Studio 2012?

Я пытаюсь использовать функцию анализа кода в Visual Studio 2012. Я просто запустил их в моем существующем проекте и обнаружил некоторые предупреждения о переполнении буфера (C6385 /C6386 ) на части, которая содержит мою собственную реализацию вычитающего PRNG Кнута (он же RAN3). Тем не менее, я не могу понять, почему это происходит, потому что это выглядит хорошо (я не вижу вне чтения / записи). Поэтому я сделал короткий эквивалент (ниже) этой части, но все еще получил те же предупреждения и не могу выяснить причину их.

С кодом выше я получил предупреждение C6386 в строке 7 и C6385 в строке 9. Что не так с этим кодом? Я что-то пропустил?

Решение

g ++ 4.8 и clang ++ 3.3 компилируют это без предупреждения или ошибки (используя -Wall -Werror). На самом деле, мы можем использовать C ++ 11 std::array И его at метод проверки границ и

как вы и утверждали, не дает за пределами доступа. Я думаю, что ваш код в порядке. VS обеспокоен тем, что линия ((21U * i) % 55U) — 1U) может привести к 0 — 1 , который будет переполнен, потому что ii является неподписанным Int. Если вы используете целые, а не беззнаковые, жалуется ли VS?

(При использовании Python ваше отображение индекса выглядит нормально:

Не должно быть никаких недопустимых ошибок, тем более что вы не достигнете -1, используя неподписанные целые.)

Другие решения

выведите значение ii, посмотрите, выходит ли оно за пределы 54
Рассчитано по этой строке
без знака int ii = ((21U * i)% 55U) — 1U;

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

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

Теперь давайте углубимся в ваш сценарий, статический анализ обнаружит, что ваша инструкция ((21U * i)% 55U) МОЖЕТ закончить вычислением до нуля, что может привести к ii = -1; это может также привести к потенциальному сбою или даже использованию безопасности в вашем программном обеспечении (атака переполнения буфера). Статический анализ не будет выполнять все итерации цикла (в отличие от динамического анализа), чтобы утверждать, что каждая ветвь кода работает хорошо.

Теперь давайте поговорим о вашем коде, у меня есть несколько рекомендаций и улучшений для вас. Пожалуйста, введите некоторую константу или #define для хранения размера вашего массива.

Теперь вместо использования жестко закодированных чисел «55» и «54» начните использовать _SIZE в своем коде. Вы можете ввести функцию полезности, чтобы получить безопасные границы для массива.

Я собираюсь изменить ваш код на основе введенной функции.

Выполните анализ кода сейчас, и он не будет предупрежден.

C/c++ — Помогите поправить код

БлогNot. Типичные ошибки начинающего разработчика на C++: проблемы и их решения

Типичные ошибки начинающего разработчика на C++: проблемы и их решения

сейчас в списке: 61 ошибка Проводя гораздо больше времени, чем мне хотелось бы, за разного рода учебными мероприятиями по программированию, я поневоле начал собирать коллекцию типовых ошибок, допускаемых начинающими разработчиками. Попробую приспособить для неё эту запись, надеюсь, список постепенно будет пополняться. до 50-60 самых типовых проблем, ну, уж никак не больше 100 :)

Конечно, я не ставлю цели рассказывать обо всех возможных синтаксических ошибках — для этого есть компилятор. Я обращу внимание скорее на типовые алгоритмические и логические неточности, допускаемые начинающими. Весь приведённый код проверен, в основном, для этого служили бесплатные сборки Microsoft Visual C++ версии 2010 или выше.

Страница длинная, поэтому воспользуйтесь оглавлением или нажмите комбинацию клавиш Ctrl+F для поиска нужного текста на странице.

Оглавление
Попытка модифицировать константный указатель

Ошибка особенно коварна тем, что всплывёт на этапе исполнения программы:

а при компиляции всё может выглядеть нормально.

Первый способ исправления — выделять память под s динамически:

Второй способ — заменить исходный указатель массивом.

Некоторые компиляторы, например, от Borland, выполняют и исходный код. Это их проблемы, а в стандарте языка такого нет.

К тому же виду ошибки можно отнести и такое:

Так нельзя, в s нет столько места, и после ввода строки возникнет ошибка. Правильно было бы сделать константный указатель на изменяемую строку, указав её размер сразу:

Теперь ввод в s будет работать, если не указывать вторым параметром метода getline размерность, большую количества выделенной памяти (255 байт в нашем случае).

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

Двойная перестановка элементов массива

Ошибка встречается при транспонировании матрицы, инверсии (переворчивании) символов строки и т.п. Суть дела в том, что вы применяете полные циклы там, где нужны только их «половинки» :)

Пример: перевернём символы строки правильно.

Типичная ошибка: при верхней границе цикла, равной L , строка останется неизменённой, так как каждая пара символов будет переставлена дважды.

Пример: транспонирование матрицы.

Типичная ошибка: если сделать в main полный двойной цикл

то ничего транспонировано не будет.

Пример: сравнить попарно все элементы вектора (программа только напечатает номера пар сравниваемых элементов)

Типовая ошибка: если сделать полный двойной цикл

то сравнения будут выполняться дважды, более того, каждый элемент будет сравниваться «сам с собой».

Пример. Меняем местами максимальные и минимальные элементы массива. Неверно:

После оператора, помеченного «1», бывший максимум уже заменён минимальным значением. Идущий следом второй оператор меняет всё обратно. Правильно, например, так:

Окно программы закрывается, я не успеваю увидеть результаты работы программы?

Если Вы не программировали остановки в программе, её и не будет!

Вставляйте оператор, ожидающий ввода с клавиатуры перед оператором return главной функции.

Варианты такого кода:

Неправильный кастинг (приведение типов)

Как известно, C++ допускает преобразования типов данных с потерей точности, в этом случае компилятор может просто «промолчать» или сгенерировать предупреждение:

Для таких случаев вполне подойдёт static_cast :

Все скобки — и треугольные, и круглые, здесь обязательны!

Если же компилятор выдаёт ошибку преобразования типа, например, при попытке присваивания структур разных типов

то можно рискнуть применить

— но лишь потому, что мы понимаем, что структуры type1 и type2 на самом деле полностью совместимы по типам данных, хотя и имеют разные теги имени структурного типа и разные наименования полей:

Обратите внимание, что присваивание через указатель var2 , как и положено, изменит значение поля исходной переменной var1 .

= вместо == (присваивание вместо сравнения)

Головная боль начинающих, особенно после Паскаля :)

Компилятор не найдёт в этом никаких проблем, максимум, сгенерирует предупреждение. Меж тем, переменной i будет присваиваться «двойка», а так как результат этой операции равен true , то всегда будет выполняться ветвь 1.

Решение очевидно — пишите правильно операцию сравнения, а именно, ==

& и | вместо && и || (побитовые операции вместо логических)

Первый оператор выполнит логическое умножение над отдельными битами значений n1 и n2 , так что для младшего байта получится

Второй оператор каждую переменную приведёт к типу bool , так что true && true = true (напомню, что true считаются все ненулевые значения).

Второй оператор мог бы быть более наглядно выполнен условием вида n1!=0 && n2!=0

Неверное использование char как int

Вот этот код ошибочно интерпретирует значение c как число (а не код символа), пытаясь прибавить к нему значение 10. В результате получится отнюдь не 11, а 59, то есть символ » ; «

Происходит это потому, что код символа «ноль» равен 48, а «единицы» 49. Правильным решением было бы сначала отнять от символа-цифры код нуля, равный 48. Неявно используется предположение, что цифровые символы закодированы идущими подряд числовыми значениями (это так во всех распространённых кодировках).

Неверное выделение/освобождение динамической памяти

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

1. Вся динамически выделяемая память должна освобождаться, в идеале «по принципу стека», то есть, занявший память последним объект освобождает её первым:

2. Если память выделялась через new , она освобождается через delete , если через new[] — через delete[] , то есть, последним оператором нужно освобождать память от массивов:

3. Не смешивайте в одной программе разные способы выделения и освобождения памяти. Если вы выделяете память с помощью функции malloc или calloc , то освобождайте её с помощью функции free , при выделении через операторы new/new[] , освобождайте с помощью delete/delete[] . В целом использование в проектах C++ «сишных» функций malloc , calloc и free не одобряется стандартом.

4. Бойтесь повторных delete , применённых к уже удалённым объектам — это одна из самых трудноуловимых ошибок. Для многих компиляторов действует неформальный «хак» — явно занулять указатель после delete , присваивая ему 0 или NULL , что зависит от компилятора:

Теперь повторные delete не опасны.

5. По возможности избегайте выделения памяти на одном уровне (например, в теле функции), а освобождения — на другом (например, в функции main ). Даже если речь о «куче» и операторах new / delete . Например:

При вызове функции f в неё передаётся копия указателя Ptr . При выделении памяти адрес помещается в эту копию, то есть, значение переменной Ptr не изменится. После возврата из функции адрес потеряется, соответственно, потеряется выделенная память и возникнет ошибка времени выполнения. Как вариант, можно делать так:

(передавать в функцию адрес указателя) или так:

(принимать в функции ссылку на указатель), но всё равно выглядит вычурно :)

Возврат из функции ссылки или указателя на локальную переменную

Да, если вам повезёт, эти 2 функции вернут результаты 1 и 2 соответственно:

На самом деле, так поступать нельзя. Локальные переменные, созданные в стеке, уничтожаются при выходе из их области видимости (в нашем случае — при выходе из функции). Таким образом, память получает статус свободной и туда могут быть записаны какие-либо новые данные. Если это не успело произойти, Вы свои 1 и 2 получите. но в сложном коде почти гарантированно однажды произойдёт сбой. Чтобы не попасть впросак, используйте естественное

Функция good2 имеет смысл в предположении, что оператор new выделяет память в куче (heap), а под локальные объекты память выделяется в стеке (stack). Также потенциально опасно, если память выделяется на одном уровне вложенности кода, а освобождается на другом.

Та же проблема имеется и с возвратом локальных строк:

Самый естественный способ исправить — в функцию передавать буфер, куда копируем строку. По возможности, при всех операциях с буфером нужно контролировать возможный выход за его границу, особенно когда работаем с заранее неизвестными размерами (типовое и не лучшее решение — использование strncpy вместо strcpy внутри функции f ).

Использование неинициализированных переменных

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

Результат может быть любым — от генерации ошибки времени исполнения до неверного расчёта, сделанного программой. Особенно часто забывают давать начальное значение количеству, сумме, максимуму или минимуму, демонстрируя тем самым, что типовые алгоритмы не учили :)

Пока мы не начали суммировать элементы массива, переменная-сумма sum отнюдь не обязана быть равной нулю!

Нулями автоматически инициализируются только:

  • статические переменные, описанные с указанием ключевого слова static ;
  • в некоторых компиляторах — глобальные переменные, описанные вне всех функций (полагаться не стоит);
  • динамические области памяти, выделяемые устаревшей сишной функцией calloc .

Два добрых совета, экономящих кучу времени:

1. Привыкнуть инициализировать объявленные переменные всегда. А указатели тоже инициализировать значением 0 или NULL (да ещё и сбрасывать обратно в эти значения после освобождения динамической памяти, связанной с указателем).

2. Объявлять переменные как можно ближе к месту их использования, благо, C++ это позволяет.

Пустой цикл из-за точки с запятой.

Часто ставят лишнюю точку с запятой, например, после открывающей части цикла:

Результат выполнения этого кода будет поистине страшен! Во-первых, здесь не написано «суммировать n элементов массива a «. Здесь написано » n раз выполнить пустой цикл, а затем прибавить к переменной sum значение i -го элемента массива a «. Во-вторых, после выхода из цикла по i , значение этой переменной может оказаться равным 3 , что приведёт ещё и к выходу за границы массива при выполнении a[i] .

Делают так и для функций. хотя вот это, к счастью, выловит любой современный компилятор:

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

Функция main не имеет типа int

Вроде бы и не ошибка, но. распространённейшее

— неверно. Согласно стандарту, функция main() должна возвращать целочисленное значение, правильно вот так:

Определение структурного типа или класса не заканчивается точкой с запятой

Вот эта маленькая программа сгенерирует, как минимум, 3 ошибки:

То же самое и при объявлении классов — не забывайте про символ ; в конце.

В нашем примере можно было и сразу же описать структурный тип и определить структуру:

Сравнение вещественных значений как целых

Проблема возникает при сравнении вещественных значений a , b в виде

Дело в том, что арифметические вычисления для чисел с плавающей запятой выполняются с некоторой погрешностью, обусловленной тем, что хранить все знаки дробной части числа в памяти бывает невозможно, например, 2/3=0.66666666666. — ряд шестёрок неизбежно будет где-то обрезан.

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

Аналогичная проблема возникает в циклах с вещественной управляющей переменной, скажем, в этом цикле

переменная x рискует «потерять» своё последнее значение, равное 1 , всё из-за тех же погрешностей. Правильный путь — прибавлять к правой границе диапазона изменения x некое малое значение, заведомо меньшее шага: for (x=0; x char ) с помощью операций отношения , == , >= и т.д. — глубоко неправильное для C++ действие. Так мы будем сравнивать не содержимое, а указатели (адреса памяти, где начинаются строки). Правильный путь — стандартная функция сравнения строк strcmp (подключить заголовки string.h ).

Неправильная запись чисел

Да-да, такое тоже бывает. Например, посмотрите, что напечатает этот код:

Просто лидирующий ноль означает число в восьмеричной системе счисления. Добавлять ноль перед числом не нужно никогда. Перед строкой — пожалуйста:

Вторая «детская» проблема, способная, тем не менее, создать жуткий головняк — применение запятой вместо точки для отделения дробной части вещественного числа.

Правило — всегда использовать точку, а не запятую при записи чисел! C++ — не Microsoft Office, а средство его написания :)

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

Принадлежит ли значение интервалу?

Проверка, которая выполняется очень часто. Допустим, есть переменная x , нужно проверить, попадает ли её значение в интервал [a,b] . Единственный правильный путь сделать это — записать

Самые распространённые неправильные способы записи собраны, некоторые из них даже компилируются, но это ничего не значит :)

Так как операция сравнения левоассоциативна (выполняется слева направо), это эквивалентно записи

дающей истинный результат при любом a .

Тоже истина для любого x .

Результатом операции «запятая» является последнее выражение, то есть, здесь проверяется только условие x

Неверный составной условный оператор

Проблема возникает, когда вы хотите запрограммировать более 2 вариантов расчёта. Обычно всё дело — в непонимании того, как работает условный оператор с несколькими ветвями расчёта.

Вот простейший пример, здесь мы определяем знак z целого числа a , по правилу:

Вот типичная ошибка такого расчёта:

Применение одного короткого и одного полного условных операторов является здесь грубой ошибкой — ведь после завершения короткого условного оператора для всех ненулевых значений a будет выполнено присваивание z=1 . Правильных вариантов этого расчета, по меньше мере, два:

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

Цукерберг рекомендует:  Вакансии ASC RePeat

— с помощью составного условного оператора, этот вариант лучше.

Вторая типичная проблема — неправильный порядок ветвей в составном операторе, из-за которого некоторые условия срабатывают «досрочно» или не срабатывают вовсе. Например, в показанном ниже фрагменте, где нужно было вывести, является ли значение x чётным или нечётным и отдельно учесть значение x=12 , напечатав для него слово «Дюжина», эта самая «дюжина» не будет напечатана никогда:

Проблема в том, что значение 12 — тоже чётное, и сработавшая ветвь «Чётное» не даст сработать ветви «Дюжина». Да и проверка условий организована явно избыточно, правильно было так:

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

При использовании оператора последовательно проверяются условия 1, 2, . ,N , если некоторое условие истинно, то выполняется соответствующий оператор и управление передается на оператор, следующий за условным. Если все условия ложны, выполняется ветвь0 , если она задана, или не выполняется ни одной ветви, если ветвь0 отсутствует. Число ветвей N не ограничено. Существенно то, что если выполняется более одного условия из N , обработано всё равно будет только первое истинное условие.

Функция не возвращает значения всегда или в некоторых случаях


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

ошибочна: для нулевого или отрицательного значения x возвращаемое значение отсутствует.

Простейший способ избежать проблемы — всегда возвращать из функции некое «значение по умолчанию»:

Некоторые компиляторы, например, Visual Studio, могут предупредить об этой проблеме:

Но для этого надо установить соответствующий уровень предупреждений компилятора. В Visual Studio это делается через команду меню Проект, Свойства, Свойства конфигурации, С/С++, Общие, Уровень предупреждений, Уровень 4. По умолчанию принят уровень 3.

Не срабатывает останов в программе или «не работает» оператор ввода

Очень частая проблема. Например, почти во всех компиляторах второй ввод через scanf будет пропущен:

А вот в таком виде всё сработает верно:

В ряде компиляторов могут быть проблемы и с таким кодом:

На самом деле, всё очень просто. «Проскакивание» ожидания ввода (паузы) происходит из-за того, что в потоке после предшествующего ввода остаются символы. Чаще всего остаётся символ новой строки \n , который попадает в поток при нажатии Enter. Оператор cin >> его просто проигнорирует, а такие функции, как getchar() , cin.get() и т.п. считывают его как первый символ в потоке и код идёт дальше, а ожидания ввода символа не происходит. Резюме — перед применением этих функций поток нужно очищать — fflush (stdin) на С или cin.sync() на C++

Вот в этом коде, если не повезёт, могут быть проблемы:

А в этом — вряд ли:

Удобней всего просто синхронизировать поток перед операциями ввода:

Экзотика. Вместо cin.get() можно просто пропустить символы:

Можно также в цикле прочитать оставшиеся символы

Выход за границы массива

Индексы массивов в C++ начинаются с нуля. Для массива из n элементов допустимые индексы лежат в диапазоне [0..n-1] . Чтение или запись информации вне выделенной памяти приводит к неопределенному поведению программы.

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

Со строками данная ошибка обусловлена, чаще всего, отсутствием в них нуль-терминатора (байта с кодом 0, пишется ‘\0’, не путать с цифрой 0 (‘0’), имеющей код 48!)

Переключатель switch без операторов break

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

Здесь для i , равного 0 , будет выведено zero one other . При i , равном 1 , будет выведено one other . То есть, без break происходит «проваливание» к следующей метке.

Бывает, что отсутствие break — не баг, а фича. Например, вот эта функция умеет возвращать 1-ю, 2-ю или 3-ю степень «двойки», а в остальных случаях результат будет равен единице.

Просто укажите комментарием, если break вам не нужны и ветви переключателя должны «проваливаться».

Градусы вместо радианов у тригонометрических функций

Очень часто к неожиданным результатам расчётов приводят вот такие ляпы в коде: Получится значение x = 0.893997, а отнюдь не 1. Просто человек имел в виду 90 градусов, а компьютер принимает аргументы тригонометрических функций в радианах. Если входная величина задана в градусах, переведите её в радианы:

Ещё лучше написать маленькую функцию, делающую такой перевод.

Неверный вызов конструктора базового класса из конструктора производного

В показанном ниже коде производный класс B пытается воспользоваться конструктором своего родителя A .

Правильный способ — использовать списки инициализации в конструкторе потомка.

Сравнение знаковых и беззнаковых значений

Вот в этом коде -1 «неожиданно» окажется больше 1:

Знаковое x приводится к беззнаковому, отчего возрастает до значения UINT_MAX — 1 (само UINT_MAX равно max(unsigned int)-1 , в разных компиляторах может обозначаться разными именами).

Решение — не сравнивать знаковые и беззнаковые значения, заранее приводя их к одному типу данных.

Вызов виртуального метода из конструктора класса

Класс A хочет для инициализации свойства x воспользоваться виртуальным методом f :

У класса-потомка B виртуальный метод реализован, но всё равно ничего не выходит. Проблема в том, что при создании объекта B , сначала создается базовая часть (класс A ), а в конструкторе базового класса ничего о классе B ещё не известно, поскольку он еще не создан, в том числе, не заполнена таблица виртуальных функций.

Решение: не вызывать виртуальные методы из конструкторов.

Неправильное выделение тела цикла или ветви условного оператора

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

Если же повторяемый оператор всего один, допустима запись тела как с операторными скобками, так и без них:

К сожалению, начинающие разработчики часто путаются и не ставят операторных скобок даже если в цикле больше одного оператора. Получается вот что:

Оператор вывода cout не имеет отношения к циклу, в цикле находится только оператор a[i]= , а выравнивание отступами для компилятора С++ ничего не значит.

Делать его всё равно нужно, это облегчает чтение программы человеком

Для исправления ошибки достаточно заключить тело цикла в операторные скобки:

Аналогичная проблема характерна и для ветвей условных операторов. Вот в этом коде

несмотря на визуальное выравнивание, ветка else относится не к первому if , а ко второму, скобки же способны решить проблему и показать разветвление кода наглядно:

Без указания дополнительных операторных скобок else всегда относится к ближайшему сверху if, для которого ветка else ещё не указана

Самый простой способ не запутаться — всегда ставить операторные скобки после ключевых слов for , while , do , if , else if или else и закрывать открытые скобки сразу же, так, чтобы закрывающая скобка была непосредственно под открывающей

или же под первой буквой ключевого слова, к которому относится открывающая скобка:

В любом случае, код, помещённый внутри цикла или ветви алгоритма сдвигается вправо пробелами и выравнивается, так что одному уровню вложенности всегда соответствует одинаковое число пробелов. Я лично для скорости печати обхожусь одним, но часто испольуют несколько пробелов или табуляцию, тем более, что в современных редакторах кода этот параметр легко настраивается. В частности, в Visual C++ пройдите в меню Сервис, Параметры, Текстовый редактор, C/C++, Табуляция и настройте так, как вам удобно.

Функция не может узнать размер параметра-массива

Функция f неудачно пытается воспользоваться конструкцией sizeof для определения размера массива-параметра:

Решение 1, самое простое и верное, подходит и для статических, и для динамических массивов. Передать размерность массива как параметр функции.

Решение 2, только для статического массива. Размер статического массива можно узнать, если принять в функции ссылку на массив и использовать template для типа данных. Увы, это сработает не во всех компиляторах и обратная совместимость кода с Си нарушается.

Решение 3, тоже подойдёт для статического массива, но не сработает с динамическим, память под который выделялась оператором new . Размер массива можно задать при использовании шаблона, вот образец:

Но это уже совсем жутик по сравнению с универсальным и обладающим обратной совместимостью способом 1 :)

Неверный порядок свойств класса при использовании списка инициализации

Есть класс со свойствами x и y , проинициализируем их с использованием списка в конструкторе:

Проблема состоит в том, что в списке инициализации конструктора объекты инициализируются в том порядке, в котором они объявлены в классе или структуре, а не в том, который указан в списке инициализации. В нашем случае сначала будет проинициализирована переменная x , а только потом y , что приведёт к неопределённости значения x .

Выход — следить за порядком объявления переменных в классе.

Функция работает с локальной копией объекта вместо объекта

Представим, что мы написали пару функций для удобной инициализации динамического массива и его удаления из памяти:

Показанный код в корне неверен тем, что при вызове функции init_array в нее передается копия указателя a , описанного в главной функции. Этот указатель ещё не проинициализирован на момент вызова функции. При выделении памяти, адрес массива будет помещён в локальную копию a внутри функции, значение a в функции main от этого не изменится. Соответственно, после выхода из функции выделенная память будет потеряна.

Первый способ решения — передать в функцию адрес указателя, используя конструкцию ** :

Второй способ — принимать в функции ссылку на указатель, конструкцию *& :

Это удобнее, не так ли? :)

Наконец, вот такая версия программы тоже способна отработать успешно —

Несоблюдение правила Большой Тройки при разработке класса или структуры

Правило Большой Тройки состоит вот в чём: если класс или структура определяет любой из следующих трёх методов:

  1. Деструктор
  2. Конструктор копирования
  3. Оператор присваивания

то он должен определить и два остальных метода! По меньшей мере, если в классе идёт работа с памятью, её динамическое выделение!

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

В рассмотренном случае конструктор по умолчанию тоже не создается автоматически.

Чтение данных «в никуда»

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

Выведется бред или возникнет Runtime Error. Правильно так:

Использование указателя как массива

Гораздо чаще, чем хотелось бы, приходится видеть в коде такое:

Конечно, так нельзя — предварительно выделите память под a. Указатель — только переменная, предназначенная для хранения адреса памяти, места под элементы массива там нет! Правильно, например, так:

Ещё надёжнее проверять, удалось ли выделить память, особенно если размерности большие:

Перепутаны символ char и строка char *

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

Скорее уж char похож на целое число int , представляя собой код символа.

При работе с однобайтовыми кодировками char занимает 1 байт памяти.

Строка char * занимает столько байт, сколько в ней символов, плюс ещё один, необходимый для хранения нуль-терминатора — байта с кодом ноль, записываемого как ‘\0’ . Не путать с цифрой ‘0’ , имеющей код 48 !

Вот практический пример. Во избежание синтаксической ошибки «находчивый» новичок явно преобразовал тип char к char * при вызове стандартного метода strcat . Всё, что он получил — Runtime-ошибку «Нарушение прав доступа».

Правильным путём было либо «присобачить» символ к строке «вручную», не забыв про нуль-терминатор:

либо создать массив из 2 символов и тогда уже использовать strcat :

Разумеется, всё это имеет смысл при условии, что в str есть «свободное место», зарезервированное статически или выделенное динамически.

Забыли, что в строке всегда должен быть нуль-терминатор

. и предусмотрен дополнительный символ (байт) для него. Вот этот код в Visual Studio вполне способен привести к краху программы.

Деструктор базового класса должен быть виртуальным!

Вот здесь я приводил эту великую мысль как пример типичной сиплюсплюснутой заумности :) Но что поделать, если это действительно так. Посмотрим простейшую иерархию из родителя A и потомка B , конструкторы и деструкторы которых печатают в консоль факт своего вызова:

Эта программа напечатает: A()B()

То есть, деструктор класса B не вызывался, соответственно, он мог не освободить память, если таковая выделялась конструктором!

Изменив деструктор класса A на

получим вывод A()B()

Таким образом, виртуальный деструктор нужен для правильного полиморфного удаления объектов.

Во избежание проблем всегда следует как минимум:

1. При наличии хотя бы одного виртуального метода объявлять виртуальным и деструктор.

2. Также его надо явно объявлять виртуальным, если класс предполагается в будущем сделать базовым.

Вызов функции, изменяющей некие величины, стоит в одном операторе с изменёнными величинами

Приведём пример простой программы, справа от операторов вывода написано, что печатается.

Как видим, обе функции успешно меняют то, что должны изменить: init — свои параметры, переданные по ссылке, а init2 — глобальную переменную global .

Но попытка вывести изменённые функциями значения в одном операторе с вызовом функций оба раза оказалась безуспешна.

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

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

Ссылка или указатель на «переехавший» объект

На словах эту распространённую проблему можно описать так:

1. В динамический объект с лимитом памяти, например, в вектор, добавляются объекты (элементы) по значению.

2. В какой-то момент запоминается ссылка или указатель на какое-либо из значений вектора.

3. В вектор добавляют ещё некоторое количество объектов.

4. В какой то момент резерв памяти иссякает, и вектор делает realloc , то есть, расширяет буфер, перенося туда свои объекты. В результате объекты меняют адрес.

5. Ранее выданные «наружу» ссылки или указатели становятся недействительными, но программа никак не отследит это.

6. Обращение по неверному указателю или ссылке приводит к ошибке времени исполнения и краху программы. Ну или просто к потере указателя, если читаем данные.

Ошибка очень трудноуловима, потому что «авария» может произойти очень далеко от места причины. Для вектора часто резервируют память ( vector::reserve ) и обычно её хватает. а однажды, при каких-то сочетаниях исходных данных, не хватит.

Совсем простой пример без векторов и классов:

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

Неверный вызов конструктора по умолчанию

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

Но эта конструкция была проинтерпретирована как объявление прототипа функции без параметров с именем a , которая возвращает значение типа A .

Правильным было бы объявление

Неявный вызов конструктора по умолчанию базового класса вместо его конструктора копирования

При копировании объектов вместо конструктора копии, у базового класса запускается конструктор по умолчанию. Вот код примера:

Проблема состоит в том, что при наследовании конструктор копирования не будет порождать вызов конструктора копирования базового класса. Программист должен явно указать, какой базовый конструктор нужно запустить. А компилятор запускает только «конструкторы по умолчанию». Правильно было бы заменить строчку

Изменение управляющей переменной цикла в теле цикла

Глупая ошибка, но встречается очень часто, вот она в немного утрированном виде:

Всего лишь помним — внутри цикла for его управляющая переменная не должна меняться иначе, чем в заголовке цикла. Для циклов do .. while и while . do счётчик меняйте всегда последним оператором тела цикла, тогда не возникнет ошибок.

Повторное вычисление границ цикла

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

У меня это выполнилось в среднем за 0.875 ms, на каждом шаге заново вычислялись верхняя граница цикла и шаг. Исправим отмеченную комментариями // часть на

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

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

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

А вот если вы пользуетесь готовой и адекватной реализацией контейнера, она сама сделает подстановку нового размера. В показанном ниже коде перебор элементов вектора выполняется с помощью итератора i , верхняя граница изменения которого «каждый раз вычисляется заново» методом end :


Равноценна по времени исполнения будет замена выделенного комментариями // цикла на

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

Цукерберг рекомендует:  Javascript - JS Чтение данных с поля формы и вывод в другое поле.
Неверный итератор после изменения контейнера внутри цикла

Посмотрим на наполнение выделенного комментариями // цикла for .

Синтаксически вполне корректный, он приведёт к краху программы. Дело в том, что после удаления элемента, итератор i начинает ссылаться на несуществующий элемент и становится невалидным. Следующее i++ уже делается «к ничему» и приводит к краху.

Типовое неверное исправление могло бы быть таким:

Останется крах при размерности списка, кратной 3, то есть, предполагающей удаление последнего элемента. То есть, ошибка станет ещё трудноуловимей. А всё дело в том, что метод erase присваивает итератору ссылку на элемент, идущий следом за удаляемым. А за удаляемым последним ничего нет, и опять инкремент ведёт за разрешённую область памяти.

Правильно будет так:

Обратите внимание, что в заголовке цикла for теперь не делается «автоматического» шага.

Можно было сделать и

Почему? Различия в работе префискного и постфиксного инкрементов, которые нужно хорошо понимать.

Неверный #define с параметрами

Вот пример для этой распространённой проблемы:

Директива #define в этой программе неверна. И ответы будут разные, причём, верен второй, полученный без #define . Дело в том, что макрос — это совсем не функция, а только чисто синтаксическая подстановка, SQUARE(x+1) превратится в x+1 * x+1 или, с учётом старшинства операций, x + x +1 .

Всегда берите аргументы #define в круглые скобки!

На всякий случай, следует избегать и строчных комментариев // при определении #define , хотя современные компиляторы обычно справляются:

А вот всё тело макроса тоже лучше всегда брать в круглые скобки, иначе выйдет вот что:

А вот так всё правильно:

Типичной проблемой является также наличие пробела между именем макроса и открывающей скобкой при описании директивы #define . Это неправильно, открывающая скобка должна идти «впритык» к имени макроса.

Вот так всё правильно:

Объявление и инициализация переменной в ветви case

Нельзя одновременно объявлять и инициализировать переменную в ветви case . Можно поместить ветвь оператора switch в операторные скобки < . >, но это чревато другими ошибками.

Объявляя переменную в одном блоке case , вы делаете её доступной для всех последующих case . Если засунуть в < . >, то область видимости ограничится фигурными скобками.

Компилятор не всегда «увидит» такую ошибку, более того, ряд компиляторов вообще не найдёт проблем в приведённом коде, но это не значит, что так нужно делать.

Несоблюдение отступов в тексте программы

Само по себе не является ошибкой, но создаёт для начинающих массу проблем, основная из которых на C++ — «потерянные» операторные скобки, порождающие кучу самых невероятных ошибок от компилятора.

Чтобы не попасть впросак и не терять времени, всегда соблюдайте несколько несложных правил.

1. Структурируйте и выравнивайте код, чтобы сделать его читаемым. Суть в том, что блоки закрываются скобкой > в том же столбце текстового редактора, в котором они открыты скобкой < . Существует, по меньшей мере, два осмысленных способа сделать это:

Мне лично нравится второй, так как экономит мне одно нажатие Enter и минимум одно нажатие пробела (если редактор текста не понимает автоматических отступов). Закрывающая скобка находится не под открывающей, но под первом символом открывающего блок оператора — это воспринимается как то же самое, стоит Вам написать 10-20 тысяч строк кода. Этот стиль неизменен практически во всех моих листингах.

Что касается количества пробелов в отступах, здесь вкусы тоже разнятся. Мне лично нравится одно нажатие пробела, как самое экономичное:

Мне не нравится также использование символа табуляции для отступа. В большинстве встроенных в IDE редакторов размер отступа табуляции невелик, скажем, равен 4, а в «простых» текстовых редакторах, вроде Блокнота, встроенного редактора Far или Notepad++, в которых тоже приходится открывать листинги, он классически равен 8. В результате программа «разъезжается» далеко вправо на сложных блоках, а вид текста зависит от размера отступа табуляции.

Поэтому в том же Visual Studio я делаю так: меню Сервис — Параметры — Все языки — Табуляция, Отступы = Блок (тогда курсор выравнивает следующую строку по предыдущей), Размер интервала табуляции и Размер отступа = 1, выбрана опция Вставлять пробелы.

2. Для соблюдения правила 1 любые блоки всегда закрывайте сразу. Такой блок, как

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

3. Если делать всё правильно, некомпилируемый код никогда не остаётся «на завтра». Блочно-модульная структура программы позволяет всегда избежать этого. Подпрограмма может быть пустой «заглушкой», вы можете использовать ничего не делающие условия, пустые блоки, комментарии, но текущий код должен компилироваться, если завтра вы не хотите половину рабочего дня затратить на восстановление в памяти недоделанного сегодня.

4. В редакторе кода используется только моноширинный шрифт, как в старых консолях или современном Studio (загляните в меню Сервис — Параметры — Среда — Шрифты и цвета). Без этого все рассуждения об отступах теряют смысл.

«Неожиданный» результат деления целых чисел

Несмотря на её банальность, проблема встречается довольно часто.

Деление целых в C++ даёт целое, забывать об этом нельзя. Правильно так:

То есть, применяем один из способов:

  • хотя бы один из аргументов превратить в вещественный тип, добавив к числу символ точки;
  • явно привести хотя бы один из аргументов к вещественному типу оператором (float)a или (double)a .
Посимвольное чтение файла обрывается при достижении буквы ‘я’

Проблема не в букве ‘я’, а в байте со значением 255. Именно это значение, рассмотренное, как char ( 0xff ) и приведённое к типу int ( 0xffffffff ) совпадёт со значением EOF , которое равно -1 . При работе под Windows с файлами в кодировке Windows-1251 код номер 255 имеет русская буква ‘я’ маленькая. В других кодировках этому коду может соответствовать другая буква. Пример неверного кода:

Решение — изменить тип переменной c :

Использование strncpy без добавления нуль-терминатора

Стандартная функция strncpy часто используется для «безопасного» копирования строк — её третий аргумент задаёт «максимальное количество символов» (на самом деле, длину буфера).

Проблема в том, что функция «забывает» ставить нуль-терминатор в конец целевой строки:

Правильно было бы поставить перед printf «ручное» завершение строки:

Сама по себе реализация этой функции такова: если исходная строка короче целевого буфера, то strncpy будет заполнять всю хвостовую часть оставшегося буфера нулями. Если строка длиннее целевого буфера, strncpy забудет выполнить её нуль-терминацию. Увы, адекватной замены методу копирования ограниченного количества символов в стандарте нет. Есть strlcpy , но это только для unix-систем.

Обяснение проблемы такое: изначально strncpy предназначалась для перевода обычных нуль-терминированных строк в строки фиксированной ширины.

Строки фиксированной ширины использовались в файловой системе Unix.

В формате фиксированной ширины строка хранится в соответствии со следующими правилами:

  • Все строки хранятся в буфере заранее заданной фиксированной ширины N;
  • Если строка имеет длину меньше N, то все лишние позиции в строке заполняются нулевыми символами;
  • Если строка имеет длину ровно N, то она полностью заполняет буфер и не заканчивается нулевым символом.
Функции atoi/atof или sscanf для перевода строки в число

Указанные функции очень популярны при преобразованиях строки в число. Тем не менее, есть 2 серьёзных проблемы, связанных с ними:

  • они не формируют отчёта об ошибочных ситуациях;
  • они имеют неопределённое поведение при переполнении.

Покажем это на примере:

Стандарт рекомендует использовать для преобразования строк в числа функции группы strto. : strtol , strtoul , strtod . Они умеют сообщать в вызывающий код о неправильном формате входных данных и устойчивы к переполнению, при котором сообщают о нём через стандартную переменную errno :

Кроме того, любую некорректную запись числа atoi / atof считают нулём.

Применение delete к объекту из стека

Вот маленькая программа, на которой хорошо видна проблема:

Суть дела в том, что оператор new выделяет память в «куче» (heap) — см. объект c . Тогда применим и delete .

Объект d создаётся в стеке ( stack ), к нему delete неприменим. Хотя при создании объекта строкой кода

конструктор будет вызван.

Каждому способу выделения памяти для переменой соответствует свой способ освобождения, например

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

Отметим также, что если для объекта, расположенного в стеке, конструктор выделяет память под какие-либо поля — например, в классе есть поле

а конструктор выделяет для него память

то в классе всё равно пишется деструктор и в нём должен быть delete[] для name . Деструктор в этом случае вызовется автоматически в конце области видимости. Нужно проектировать программу так, чтобы подобный вызов был к месту.

Если же время жизни объекта нужно контролировать независимо от текущей области видимости и освобождать его директивно, явным вызовом деструктора, то такой объект следует размещать в «куче» и выделять память оператором new .

Наконец, в классе можно создать метод с условным именем erase , который закроет все внутренние объекты и освободит занимаемую ими память. Деструктор просто вызывает метод erase , но erase можно вызвать и без деструктора.

См. также в этой заметке про стек и кучу.

Пути к файлам без двойного бэкслеша

Очень частая проблема у начинающих, особенно, если они «пришли» с другого, не си-подобного языка:

В этой программе файл никогда не будет открыт и прочитан, даже если он существует и содержит данные. Дело в том, что обратный слеш внутри двойных кавчек для C и C++ — спецсимвол, например, \t означает табуляцию, а \n — перевод строки. Если внутри двойных кавчек нужен символ «обратный слэш», как известно, служащий в Windows для разделения имён папок при записи пути к файлам, он удваивается: \\.

Поэтому правильно будет так:

«Удвоение» последней строки файла

Возьмём небольшую программу, построчно читающую текстовый файл:

Сам файл C:\temp\data.txt состоит из пары строк и пустой строки в конце:

«Удивительным образом» наша программа «удвоит» string 2 , которая будет напечатана дважды. Проблема в том, что после чтения string 2 конец файла ещё не достигнут, а ввод-вывод через stdio буферизован. последний шаг цикла прочитает пустую строку, а в буфере всё ещё будет string 2 , которая и выведется повторно вместо пустой строки. Проверьте сами — если удалить из файла данных пустую строку в конце —

— никакого «удвоения» не будет. Встречается вот такое решение — — но оно грозит, наоборот, «потерять» последнюю строку файла, если после неё нет пустой. Хотя при форматном чтении данных, скажем, целых чисел из файла, такой подход бывает применим:

(лучше посмотреть здесь про чтение чисел scanf’ом из файла со «смешанным» форматом).

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

«Лишние» пустые строки при построчном выводе данных

С программкой из этого примера связана ещё одна типовая проблема — прочитанные из файла строки при выводе почему-то содержат лишние пустые строки между строками данных. Файл данных: Вывод:

Всё объясняется просто — fgets читает строку файла вместе с символом перевода строки (точней, под Windows — с парой символов \r\n , интерпретируемых как один), а puts добавляет к выводимой строке ещё один перевод строки. Так что выводите другим методом или удаляйте из прочитанной fgets ‘ом строки последний символ:

Переопределённый оператор не возвращает экземпляр или ссылку на экземпляр класса

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

Оператор += изменяет только текущий объект, но тоже может встретиться в цепочке вычислений, скажем, A d=*a+=*b; Вполне достаточно, если он вернёт *this для возможности выполнения дальнейших расчётов.

А вот бинарному сложению после выполнения *a + *b нужно вернуть именно новый объект класса, чтобы можно было, например, продолжить цепочку вычисления, прибавив той же функцией-оператором ещё c : *a + *b + c .

Формально не запрещено, сделать и такое бинарное сложение:

Но выражение над экземплярами класса *a + *b + с работать уже не будет, только *a + *b .

Если в коде примера вы замените тип возвращаемого значения у операторных функций с A & на A , тоже будет работать.

Из текстового файла по формату читается только первое число и всё зацикливается!

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

если файл data.txt в текущей папке вот такой:

(прочитается только первое число).

Решение — либо убрать русскую локаль, для которой разделитель целой и дробной части числа — не точка, а запятая:

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

Если читать средствами C++ (потоки), а Си-совместимыми — аналогично.

Сцепление Си-строк без выделения памяти

Сначала воспроизведем типичную ошибку начинающих:

Так как функция strcat не выделяет память, поведение такого кода непредсказуемо и с вероятностью 99% приведёт к ошибке времени исполнения программы! А вот такое сцепление строк сработает:

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

Из той же оперы — использование указателя как массива. Не забывайте также, что вместо динамических массивов и строк Си практичнее использовать контейнер vector и класс string.

Неверное использование тернарного оператора

Тернарный условный оператор (conditional expression) вида условие?оператор1:оператор2; очень удобен, но способен создать ряд проблем при неаккуратном его применении.

При работе с ним нужно учесть 2 момента.

Во-первых, типом тернарного оператора будет наиболее общий тип его двух последних операндов. Что значит наиболее общий? Например, для int и short общим типом будет int , то есть, наиболее общий тип — это такой тип, к которому могу быть приведены оба операнда.

Вполне возможны ситуации, когда общего типа нет и возникает ошибка, например:

Кроме того, если в тернарном операторе происходит преобразование типов к наиболее общему, то тернарный оператор является rvalue . Если же нет, то lvalue (что это значит?).

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

Например, вот эта маленькая программа корректно использует тернарный оператор для вывода пробела или перевода строки в cout (массив печатается по 2 элемента в одной строке консоли):

Без дополнительных скобок в операторе вывода, то есть, при коде

вывод будет воспринят как

и окажется неправильным.

Присваивание объектов класса, содержащих динамические свойства, без написания конструктора копирования

Если в классе есть динамическое свойство, заданное указателем, при копировании объекта класса, под который выделялась динамическая память в «куче», скопируется только адрес памяти, содержащийся в указателе.

Если затем объект 2, бывший справа от знака «=», будет удалён, перестанет работать и ссылка на память из объекта 1:

Для корректной работы нужно опеределить в классе оператор присваивания и конструктор копирования (см. правило большой тройки).

Тогда можно переписать динамическое свойство s в новую память, которую мы выделим в куче (код простейший, не содержит проверок того, удалось ли выделить память):

«Опасная» проверка булевой переменной в условии

Безобидное на первый взгляд

может привести к трудноуловимой ошибке. Например, в ряде компиляторов константа true==-1 , а в C++ принято, что всё, что не 0 , равно true и только 0 — это false .

Соответственно, всегда безопасней

Неверное выделение памяти под динамическую матрицу

Отметим как отдельную ошибку. Правильный путь таков:

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

Освобождать память, в идеале, нужно в порядке, обратном выделению. Вот пример кода.

12.02.2015, 12:38; рейтинг: 81208

Основы C++ — урок 1

Здравствуй, уважаемый читатель сайта CodeLessons.ru! Сейчас пойдет речь о самых важных моментах в C++ на которых и основана любая программа. Мы узнаем главные части программы, а также и назначение каждой из них. Для начала вам потребуется установленная >

Видео урок

Основные особенности кода на C++

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

  • каждая команда заканчивается точкой с запятой ; ;
  • в названии команд и прочих инструкций не может быть пробелов, а также они не могут начинаться с цифр;
  • язык С++ чувствителен к регистру символов. То есть, CODE, CoDe и code могут выполнять абсолютно разные задачи;


Это и есть главные правила, на которых основан фундамент программирования на C++.

Начало работы с C++

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

Теперь давайте разберем главные аспекты C++ на примере. Для этого мы запустим тестовою программу, а затем шаг за шагом рассмотрим структуру программ в C++:

Подборка онлайн компиляторов: запускаем и тестируем код прямо в браузере

Почти все разработчики рано или поздно сталкиваются с необходимостью запустить или быстро проверить какой-то код, но не все знают, что для такой простой задачи совсем не обязательно запускать тяжёлые десктопные IDE или прикладные компиляторы. Достаточно воспользоваться онлайн-инструментами, которые позволяют всё сделать намного быстрее: Ctrl+C, Ctrl+V, Run, вжух — и вывод программы уже перед вашими красноватыми глазами.

Цукерберг рекомендует:  Стартапы - От идеи до прототипа.

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

Koding

Koding.com не является онлайн-компилятором в привычном смысле. Каждый пользователь сервиса может создать в облаке несколько полноценных виртуальных машин под управлением Ubuntu 14.04, на которых может сделать всё, что пожелает, в том числе — скомпилировать код. Все популярные языки поддерживаются по умолчанию, но вы с лёгкостью сможете добавить свои.

Кроме панели управления своим сервером, в интерфейсе доступна удобная IDE и окошко терминала. Koding является самым универсальным средством, далее мы рассмотрим более простые и специализированные варианты.

IdeOne

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

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

Для тех, у кого нет девушки, создатели предусмотрели компиляцию кода на языке Brainfuck.

JDoodle

Ещё один онлайн-компилятор, который поддерживает множество языков, в том числе и тех, которые вы не найдете во многих других онлайн-компиляторах. Приятной особенностью JDoodle является возможность совместной работы — просто отправьте ссылку на вашу текущую сессию и плодите баги с двойной скоростью!

jsFiddle

Пусть название вас не обманывает — jsFiddle создан не только для JavaScript. Этот онлайн-редактор для фронтенда позволяет проверить любое сочетание JavaScript, HTML и CSS. Разумеется, есть поддержка разных фреймворков, например, jQuery, Vue, React, TypeScript, а также CSS-препроцессоров вроде SCSS. Для удобства вы можете выбрать привязку клавиш из любимого редактора. Правда, только в том случае, если ваш любимый редактор — Vim, Emacs или Sublime Text.

CodePad

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

Главным его достоинством является простота и лёгкость: сайт будет быстро работать даже при медленном интернете. Предусмотрено автоподключение стандартных заголовков, а также интеграция с Vim или Emacs.

Из минусов можно назвать полное отсутствие подсветки синтаксиса при вводе кода в форму. Впрочем, при просмотре уже сохранённой записи подсветка присутствует.

GCC GodBolt

GCC GodBolt — интерактивный компилятор языка С++. Попал в эту подборку по той причине, что имеет простой интерфейс, а также большое количество настроек, в том числе для опций, регулируемых с помощью ключей.

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

SandBox и PHPFiddle

И SandBox, и PHPFiddle позволяют в один клик запустить PHP код и следом получить его вывод. Но несмотря на одно и то же предназначение, они имеют различия, которые могут стать критичными в той или иной ситуации.

PHPFiddle имеет более удобный и современный дизайн, на нём установлена актуальная версия PHP, а также есть возможность форматировать вывод скрипта HTML-разметкой.

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

Форум русскоязычного сообщества Ubuntu

Считаете, что Ubuntu недостаточно дружелюбна к новичкам?
Помогите создать новое Руководство для новичков!

Автор Тема: Поправьте код на C++ (Прочитано 439 раз)

0 Пользователей и 1 Гость просматривают эту тему.

Страница сгенерирована за 0.081 секунд. Запросов: 24.

© 2012 Ubuntu-ru — Русскоязычное сообщество Ubuntu Linux.
© 2012 Canonical Ltd. Ubuntu и Canonical являются зарегистрированными торговыми знаками Canonical Ltd.

Код C, который не будет работать в C++?

Вырезка из Википедии:

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

И у Лафоре встречал такое заявление, что аки не каждый код на C будет скомпилирован компилятором C++. Спорить-то и нечего, но сколько помню, то все функции языка Си поддерживаются в C++. Обратная совместимость же!

Но можно ли узнать конкретно какой код не скомпилируется?

  • Вопрос задан более трёх лет назад
  • 1056 просмотров

посмотрите что-то из выделения памяти. malloc вроде работает и там и там, а другие варианты могут давать сбой.
Линковка в С++ немного отличается.

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

Массивы переменной длинны (VLA).

Но всё зависит от компилятора и строгости следования стандарту. GNU компилятор, например, вводит такие массивы для C++ в расширениях.

Ещё в C есть restrict переменные, регистровые переменные, которых нет с C++.

Как закончить код на С++

Я бы хотел, чтобы мой код на С++ прекратил работу, если выполнено определенное условие, но я не уверен, как это сделать. Поэтому в любой момент, если инструкция if истинна, завершите код следующим образом:

Существует несколько способов, но сначала вам нужно понять, почему важно очистить объект, и поэтому причина std::exit является маргинальной среди С++ программисты.

RAII и Stack Unwinding

С++ использует идиому под названием RAII, которая в простых терминах означает, что объекты должны выполнять инициализацию в конструкторе и очищать в деструкторе, Например, std::ofstream класс [может] открыть файл во время конструктора, тогда пользователь выполняет на нем выходные операции и, наконец, в конце своего жизненного цикла, обычно определяемого его областью, деструктор называется тем, что существенно закрывает файл и сбрасывает любое записанное содержимое на диск.

Что произойдет, если вы не дойдете до деструктора, чтобы сбросить и закрыть файл? Кто знает! Но, возможно, он не будет записывать все данные, которые он должен был записать в файл.

Например, рассмотрим этот код

Что происходит в каждой возможности:

  • Возможность 1: Возврат существенно оставляет текущую область действия, поэтому он знает о конце жизненного цикла os , тем самым называя его деструктор и делая правильную очистку, закрывая и промывая файл до диск.
  • Возможность 2: Выбрасывание исключения также заботится о жизненном цикле объектов в текущей области действия, тем самым делая правильную очистку.
  • Возможность 3: Здесь стекается разматывание в действии! Несмотря на то, что исключение выбрано в inner_mad , разматыватель будет проходить через стек mad и main для правильной очистки, все объекты будут разрушены должным образом, включая ptr и os .
  • Возможность 4: Ну, здесь? exit является функцией C и не знает и не совместим с идиомами С++. Он не выполняет выполнять очистку ваших объектов, включая os в той же области. Таким образом, ваш файл не будет закрыт должным образом, и по этой причине контент никогда не будет записан в него!
  • Другие возможности:. Он просто оставит основную область, выполнив неявный return 0 и, таким образом, получив тот же эффект, что и возможность 1, т.е. правильная очистка.

Но не будьте так уверенны в том, что я только что сказал вам (в основном возможности 2 и 3); продолжить чтение, и мы узнаем, как выполнить правильную очистку на основе исключений.

Возможные пути завершения

Возврат из main!

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

Вызывающая ваша программа и, возможно, операционная система могут захотеть узнать, успешно ли выполнена ваша программа или нет. По этой же причине вы должны вернуть либо ноль, либо EXIT_SUCCESS , чтобы сообщить, что программа успешно завершена, и EXIT_FAILURE , чтобы сигнализировать о завершении неудачной программы, любая другая форма возвращаемого значения определяется реализацией (§18.5/8).

Однако вы можете быть очень глубоки в стеке вызовов и вернуть все это может быть больно.

[Не создавать] исключение

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

Но вот catch! Это реализация определяет, выполняется ли разматывание стека, когда исключение броска не обрабатывается (по предложению catch (. )) или даже если у вас есть noexcept в середине стека вызовов. Об этом говорится в §15.5.1 [except.terminate]:

В некоторых ситуациях обработка исключений должна быть отменена для менее тонких методов обработки ошибок. [Примечание: Эти ситуации:

, когда механизм обработки исключений не может найти обработчик для генерируемого исключения (15.3) или когда поиск обработчика (15.3) встречает самый внешний блок функции с noexcept -спецификацией, который не допускает исключения (15.4) или [. ]

В таких случаях вызывается std:: terminate() (18.8.3). В ситуации, когда соответствующий обработчик не найден, он определяется реализацией независимо от того, разрывается ли стек до того, как std:: terminate() вызывается [. ]

Итак, мы должны его поймать!

Бросьте исключение и поймайте его на главном!

Так как неотображаемые исключения могут не выполнять разворачивание стека (и, следовательно, не будут выполнять надлежащую очистку), мы должны поймать исключение в основном, а затем вернуть статус выхода ( EXIT_SUCCESS или EXIT_FAILURE ).

Таким образом, возможно, хорошая настройка:

[Не выполнять] std:: exit

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

Это применяется в §3.6.1/4 [basic.start.init]:

Завершение работы программы без выхода из текущего блока (например, путем вызова функции std:: exit (int) (18.5)) не уничтожает объекты с автоматическим временем хранения (12.4). Если std:: exit вызывается для завершения программы во время уничтожения объекта со статикой или продолжительностью хранения потоков, программа имеет поведение undefined.

Подумайте об этом сейчас, зачем вам это делать? Сколько объектов вы пострадали от боли?

Другие [как плохие] альтернативы

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

  • std::_Exit вызывает нормальное завершение программы и что оно.
  • std::quick_exit вызывает обычное завершение программы и вызывает std::at_quick_exit , никакая другая очистка не выполняется.
  • std::exit вызывает нормальное завершение программы, а затем вызывает std::atexit . Другие виды очистки выполняются, например, вызов деструкторов статических объектов.
  • std::abort вызывает ненормальное завершение программы, никакая очистка не выполняется. Это следует назвать, если программа завершилась действительно, действительно неожиданным образом. Он ничего не сделает, кроме как сигнализирует ОС об аномальном завершении. В этом случае некоторые системы выполняют сброс ядра.
  • std::terminate вызывает std::terminate_handler , который звонит std::abort по умолчанию.

Подборка онлайн компиляторов: запускаем и тестируем код прямо в браузере

Почти все разработчики рано или поздно сталкиваются с необходимостью запустить или быстро проверить какой-то код, но не все знают, что для такой простой задачи совсем не обязательно запускать тяжёлые десктопные IDE или прикладные компиляторы. Достаточно воспользоваться онлайн-инструментами, которые позволяют всё сделать намного быстрее: Ctrl+C, Ctrl+V, Run, вжух — и вывод программы уже перед вашими красноватыми глазами.

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

Koding

Koding.com не является онлайн-компилятором в привычном смысле. Каждый пользователь сервиса может создать в облаке несколько полноценных виртуальных машин под управлением Ubuntu 14.04, на которых может сделать всё, что пожелает, в том числе — скомпилировать код. Все популярные языки поддерживаются по умолчанию, но вы с лёгкостью сможете добавить свои.

Кроме панели управления своим сервером, в интерфейсе доступна удобная IDE и окошко терминала. Koding является самым универсальным средством, далее мы рассмотрим более простые и специализированные варианты.

IdeOne

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

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

Для тех, у кого нет девушки, создатели предусмотрели компиляцию кода на языке Brainfuck.

JDoodle

Ещё один онлайн-компилятор, который поддерживает множество языков, в том числе и тех, которые вы не найдете во многих других онлайн-компиляторах. Приятной особенностью JDoodle является возможность совместной работы — просто отправьте ссылку на вашу текущую сессию и плодите баги с двойной скоростью!

jsFiddle

Пусть название вас не обманывает — jsFiddle создан не только для JavaScript. Этот онлайн-редактор для фронтенда позволяет проверить любое сочетание JavaScript, HTML и CSS. Разумеется, есть поддержка разных фреймворков, например, jQuery, Vue, React, TypeScript, а также CSS-препроцессоров вроде SCSS. Для удобства вы можете выбрать привязку клавиш из любимого редактора. Правда, только в том случае, если ваш любимый редактор — Vim, Emacs или Sublime Text.

CodePad

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

Главным его достоинством является простота и лёгкость: сайт будет быстро работать даже при медленном интернете. Предусмотрено автоподключение стандартных заголовков, а также интеграция с Vim или Emacs.

Из минусов можно назвать полное отсутствие подсветки синтаксиса при вводе кода в форму. Впрочем, при просмотре уже сохранённой записи подсветка присутствует.

GCC GodBolt

GCC GodBolt — интерактивный компилятор языка С++. Попал в эту подборку по той причине, что имеет простой интерфейс, а также большое количество настроек, в том числе для опций, регулируемых с помощью ключей.

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

SandBox и PHPFiddle

И SandBox, и PHPFiddle позволяют в один клик запустить PHP код и следом получить его вывод. Но несмотря на одно и то же предназначение, они имеют различия, которые могут стать критичными в той или иной ситуации.

PHPFiddle имеет более удобный и современный дизайн, на нём установлена актуальная версия PHP, а также есть возможность форматировать вывод скрипта HTML-разметкой.

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

Форум русскоязычного сообщества Ubuntu

Считаете, что Ubuntu недостаточно дружелюбна к новичкам?
Помогите создать новое Руководство для новичков!

Автор Тема: Поправьте код на C++ (Прочитано 440 раз)

0 Пользователей и 1 Гость просматривают эту тему.

Страница сгенерирована за 0.069 секунд. Запросов: 24.

© 2012 Ubuntu-ru — Русскоязычное сообщество Ubuntu Linux.
© 2012 Canonical Ltd. Ubuntu и Canonical являются зарегистрированными торговыми знаками Canonical Ltd.

C/c++ — Помогите поправить код

Вот моя программа

#define MIN_VALUE 21
#define MAX_VALUE 56

void main()
<
int x = 0;
int result = MIN_VALUE
http://vkontakte.ru/id84076405

evilCode Дата: Понедельник, 18 Октября 2010, 18:04 | Сообщение # 2
попробуй так

200?’200px’:»+(this.scrollHeight+5)+’px’);»> #include
#include

#define MIN_VALUE 21
#define MAX_VALUE 56

void main()
<
int x = 0;
printf(«Input a number \n»);
scanf(«%d», &x);
int result = MIN_VALUE
evilCode aka sanyok31

karuy Дата: Понедельник, 18 Октября 2010, 18:09 | Сообщение # 3
Жаль нет близко компа чтоб прогу набрать. У тебя result всегда равен нулю ибо х сначала тож равен нулю. Перенеси вторую строчку з резалтам после ввода х.
lvovand Дата: Понедельник, 18 Октября 2010, 18:09 | Сообщение # 4
у вас определась переменная result,
int result = MIN_VALUE >= x && x
Разработка и продвижение сайтов. Дизайн
evilCode Дата: Понедельник, 18 Октября 2010, 18:12 | Сообщение # 5
мой вариант рабочий
evilCode aka sanyok31
Assassin5 Дата: Понедельник, 18 Октября 2010, 18:17 | Сообщение # 6
Вот, разобрался.
Так правильно:

#define MIN_VALUE 21
#define MAX_VALUE 56

void main()
<
int x;
int result;
printf(«Input a number \n»);
scanf(«%d», &x);
result = MIN_VALUE
http://vkontakte.ru/id84076405

evilCode Дата: Понедельник, 18 Октября 2010, 18:22 | Сообщение # 7
там опечатка была. я исправил
evilCode aka sanyok31

Assassin5 Дата: Понедельник, 18 Октября 2010, 18:25 | Сообщение # 8
а, ну можно и так. Только у меня покороче вышло. Экономия ресурсов xD. Препод придирается.
http://vkontakte.ru/id84076405
evilCode Дата: Понедельник, 18 Октября 2010, 18:28 | Сообщение # 9
можно ещё короче ))
evilCode aka sanyok31
ezhickovich Дата: Понедельник, 18 Октября 2010, 19:20 | Сообщение # 10
Assassin5, ты используешь тернарную операцию ?: — это тоже условный оператор.

Вот решение без условий:

#define MIN_VALUE 21
#define MAX_VALUE 56

int main ()
<
int x = 0;

scanf («%d», &x);
printf («Number %d is inside range %d. %d : %d \n», x,
MIN_VALUE, MAX_VALUE, MIN_VALUE

Я: О великий повелитель этой ничтожной вселенной — сокращённо ЁЖ!

Понравилась статья? Поделиться с друзьями:
Все языки программирования для начинающих