Каков самый простой стандарт, способный создать Segfault в C?

Думаю, в этом все сказано. Примером может служить пример, охватывающий большинство стандартов от C89 до C11. Я, хотя и об этом, но, я думаю, это просто поведение undefined:

#include <stdio.h>

int main( int argc, char* argv[] )
{
  const char *s = NULL;
  printf( "%c\n", s[0] );
  return 0;
}

EDIT:

Поскольку некоторые голоса запрашивали разъяснение: я хотел иметь программу с обычной ошибкой программирования (самым простым из которых я мог бы считать segfault), который гарантирован (стандартным) для прерывания. Это немного отличается от минимального вопроса о segfault, который не заботится об этом страховании.

Ответы

Ответ 1

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

Если мы посмотрим на черновик стандарта C99 & 3.4.3 неопределенного поведения, который описан в разделе "Термины, определения и символы" в параграфе 1, то там будет сказано (выделение выделено в будущем):

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

и в пункте 2 говорится:

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

Если, с другой стороны, вам просто нужен метод, определенный в стандарте, который вызовет ошибку сегментации в большинстве Unix-подобных систем, тогда raise(SIGSEGV) должен достичь этой цели. Хотя, строго говоря, SIGSEGV определяется следующим образом:

SIGSEGV неверный доступ к хранилищу

и раздел 7.14. Обработка сигналов <signal.h> гласит:

Реализация не должна генерировать какие-либо из этих сигналов, кроме как в результате явных вызовов функции повышения. Дополнительные сигналы и указатели на необъявленные функции, с определениями макросов, начинающимися, соответственно, с букв SIG и заглавной буквы или с SIG_ и заглавной буквы 219), также могут быть определены реализацией. Полный набор сигналов, их семантика и обработка по умолчанию определяется реализацией; все номера сигналов должны быть положительными.

Ответ 2

raise() можно использовать для повышения segfault:

raise(SIGSEGV);

Ответ 3

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

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

int main()
{
    *(int*)0 = 0;
}

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

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

Ответ 4

Правильная программа не создает segfault. И вы не можете описать детерминированное поведение неправильной программы.

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

Ответ 5

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

Но этот самый короткий, я могу получить этот segfault в моей системе:

main(){main();}

(Я компилирую с помощью gcc и -std=c89 -O0).

И, кстати, действительно ли эта программа вызывает undefined bevahior?

Ответ 6

На некоторых платформах стандартно-совместимая программа C может выйти из строя с ошибкой сегментации, если она запрашивает слишком много ресурсов из системы. Например, выделение большого объекта с помощью malloc может показаться успешным, но позже, когда объект будет доступен, он будет аварийно завершен.

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

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

Сигнал SIGSEGV может быть явно выражен, но в стандартной библиотеке C. нет символа SIGSEGV.

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

Ответ 7

Большинство ответов на этот вопрос обсуждают ключевой момент, который: Стандарт C не включает концепцию ошибки сегментации. (Так как C99 включает в себя номер сигнала SIGSEGV, но он не определяет каких-либо обстоятельств, когда этот сигнал доставляется, кроме raise(SIGSEGV), который, как обсуждалось в других ответах, не учитывается.)

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

Ошибки сегментации определяются другим стандартом, POSIX. Эта программа гарантированно спровоцирует либо ошибку сегментации, либо функционально эквивалентную "ошибку шины" (SIGBUS), в любой системе, полностью соответствующей POSIX.1-2008, включая опции "Защита памяти" и "Расширенные параметры реального времени", при условии, что вызовы sysconf, posix_memalign и mprotect завершаются. Мое чтение C99 заключается в том, что эта программа имеет реализацию, отличную от реализации (не undefined!), Учитывая только этот стандарт, и поэтому она соответствует, но не строго соответствует.

#define _XOPEN_SOURCE 700
#include <sys/mman.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

int main(void)
{
    size_t pagesize = sysconf(_SC_PAGESIZE);
    if (pagesize == (size_t)-1) {
        fprintf(stderr, "sysconf: %s\n", strerror(errno));
        return 1;
    }
    void *page;
    int err = posix_memalign(&page, pagesize, pagesize);
    if (err || !page) {
        fprintf(stderr, "posix_memalign: %s\n", strerror(err));
        return 1;
    }
    if (mprotect(page, pagesize, PROT_NONE)) {
        fprintf(stderr, "mprotect: %s\n", strerror(errno));
        return 1;
    }
    *(long *)page = 0xDEADBEEF;
    return 0;
}

Ответ 8

Трудно определить метод для сегментации ошибки программы на платформах undefined. Ошибка сегментации - это свободный термин, который не определен для всех платформ (например, простых небольших компьютеров).

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

Кроме того, ограничение операционных систем на "unix like" ОС, надежный метод для приема сигнала SIGSEGV составляет kill(getpid(),SIGSEGV)

Как и в большинстве проблем с кросс-платформой, каждая платформа может (как правило, имеет) иметь другое определение seg-faulting.

Но чтобы быть практичным, текущие mac, lin и win OSes будут segfault на

*(int*)0 = 0;

Кроме того, это не плохое поведение, чтобы вызвать segfault. Некоторые реализации assert() вызывают сигнал SIGSEGV, который может генерировать основной файл. Очень полезно, когда вам нужно вскрывать.

То, что хуже, чем вызвать segfault, скрывает его:

try
{
     anyfunc();
}
catch (...) 
{
     printf("?\n");
}

который скрывает происхождение ошибки, и все, что вам нужно сделать, это:

?

.

Ответ 9

 main;

Вот оно.

На самом деле.

По сути, это определяет main как переменную. В Си переменные и функции являются символами - указателями в памяти, поэтому компилятор не различает их, и этот код не выдает ошибку.

Однако проблема заключается в том, как система запускает исполняемые файлы. Короче говоря, стандарт C требует, чтобы все исполняемые файлы C имели встроенную в них точку входа для подготовки среды, которая в основном сводится к "call main".

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

Для иллюстрации вот (часть) objdump полученного файла:

# (unimportant)

Disassembly of section .text:

0000000000001020 <_start>:
    1020:   f3 0f 1e fa             endbr64 
    1024:   31 ed                   xor    %ebp,%ebp
    1026:   49 89 d1                mov    %rdx,%r9
    1029:   5e                      pop    %rsi
    102a:   48 89 e2                mov    %rsp,%rdx
    102d:   48 83 e4 f0             and    $0xfffffffffffffff0,%rsp
    1031:   50                      push   %rax
    1032:   54                      push   %rsp
    1033:   4c 8d 05 56 01 00 00    lea    0x156(%rip),%r8        # 1190 <__libc_csu_fini>
    103a:   48 8d 0d df 00 00 00    lea    0xdf(%rip),%rcx        # 1120 <__libc_csu_init>

    # This is where the program should call main
    1041:   48 8d 3d e4 2f 00 00    lea    0x2fe4(%rip),%rdi      # 402c <main> 
    1048:   ff 15 92 2f 00 00       callq  *0x2f92(%rip)          # 3fe0 <[email protected]_2.2.5>
    104e:   f4                      hlt    
    104f:   90                      nop

# (nice things we still don't care about)

Disassembly of section .data:

0000000000004018 <__data_start>:
    ...

0000000000004020 <__dso_handle>:
    4020:   20 40 00                and    %al,0x0(%rax)
    4023:   00 00                   add    %al,(%rax)
    4025:   00 00                   add    %al,(%rax)
    ...

Disassembly of section .bss:

0000000000004028 <__bss_start>:
    4028:   00 00                   add    %al,(%rax)
    ...

# main is in .bss (variables) instead of .text (code)

000000000000402c <main>:
    402c:   00 00                   add    %al,(%rax)
    ...

# aaand that it! 

PS: это не сработает, если вы скомпилируете в плоский исполняемый файл. Вместо этого вы будете вызывать неопределенное поведение.

Ответ 10

Самая простая форма, учитывающая наименьшее количество символов:

++*(int*)0;