Безопасное программирование на С

Я заметил, что мой компилятор C (gcc) позволит мне делать такие вещи, как:

#include <stdio.h>
main(){
    short m[32768];
    short y = -1;
    short z = -1;
    printf("%u\n", y);
    m[y] = 12;
    printf("%d\n%d\n", y, m[z]);
}

Когда я запускаю его, он выплевывает:

4294967295
12
12

Что мне кажется немного озадаченным.

Прежде всего, безопасно ли мне запускать такие программы? Есть ли вероятность, что я случайно напишу поверх операционной системы (я использую OS X в случае, если это актуально)?

Кроме того, я ожидал, по крайней мере, какой-то ошибки segfault, с которой я столкнулся в прошлом, но тихо игнорируя такую ​​ошибку, действительно меня пугает. Почему эта программа не меняет на меня?

И, наконец, из любопытства (это может быть самый глупый вопрос), есть ли метод безумия? Могу ли я ожидать, что все компиляторы ANSI C будут работать таким образом? Как насчет gcc на разных платформах? Является ли макет памяти четко определенным, что он может быть использован (возможно, если вы собираетесь писать кросс-платформенный обфускационный код)?

Ответы

Ответ 1

Язык C определяет поведение определенных программ как "undefined". Они могут делать все. Мы будем называть такие программы ошибочными.

Одна из них - это программа, которая обращается за пределами объявленных/выделенных границ массива, что очень хорошо делает ваша программа.

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

Простой ответ: "Не записывайте ошибочные программы". Тогда поведение, которое вы видите, будет иметь смысл "C".

В этом конкретном случае с вашим конкретным компилятором работает индексирование массива "сортировка": вы индексируете вне массива и получаете некоторое значение. Пространство, выделенное m, находится в кадре стека; m [0] находится в каком-либо месте в кадре стека, а также "m [-1]" на основе машинной арифметики, объединяющей адрес массива и индекс, поэтому segfault не происходит и доступ к ячейке памяти. Это позволяет скомпилированной программе читать и записывать эту ячейку памяти... как ошибочную программу. В принципе, скомпилированные C-программы не проверяют, не выходит ли ваш доступ к массиву за пределы.

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

Ответ 2

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

Ваш пример: Нет. Абсолютно нет. Почему бы вам даже попробовать? Что вы ожидаете от этого? Более общие примеры с использованием отрицательных индексов - если они разыгрываются в законную память, тогда это нормально.

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

Слепая удача. (на самом деле не изящно - как приятно объяснил Ира Бакстер)

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

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

Можно ли ожидать, что все компиляторы ANSI C будут работать таким образом?

Да.

Как насчет gcc на разных платформах?

Да

Является ли макет памяти четко определенным, что он может быть использован (возможно если вы собираетесь писать кросс-платформенный запущенный код)?

Да, но я не уверен, действительно ли вы захотите положиться на него.

Ответ 3

  • "Безопасный", с точки зрения возможного повреждения ОС - да, в основном, большинство "современных" операционных систем реализуют некоторую форму "защиты хранилища", так что своенравная или вредоносная программа не может плохо работать с ОС или другие программы. (Но "в основном" указывает, что ни одно программное обеспечение длиной более 50 строк не идеально, и всегда есть странная вероятность, что вы найдете щель в доспехах.)
  • "Безопасно" с точки зрения вашей программы "вести себя" должным образом? Welllllllll, нет, в основном. Здесь "в основном" относится к тому, что знающие (я не буду говорить "умные" ) программисты будут иногда делать "странные" вещи, такие как использование индекса отрицательного массива, когда они знают, как работают компилятор и среда выполнения, и должны эффективно совершить нечто, что несколько вне нормы. Но они (надеюсь) делают это, зная, что "трюк", который они используют, очень зависит от системы/компилятора.

Но поскольку @jb указывает "безопасное программирование на С", это оксюморон.

Ответ 4

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

Ответ 5

Если программа запрашивает юридический адрес в памяти (который может выполняться m [-1], так как его поведение undefined), вы не получите segfault... особенно если вы просите короткое время будет вписываться в большинство длин слов. Это действительно плохая идея, вы можете легко перезаписать что-то на диске, хотя ядро ​​защищено, если вы не запускаете свой код при запуске.

Я не уверен, почему вы напечатали адрес -1, хотя.