Каков самый простой стандартный способ соответствия для получения Segfault в C?


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

#include <stdio.h>

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

EDIT:

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

9 52

9 ответов:

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

Если мы посмотрим на стандарте C99 проект стандарта §3.4.3 неопределенное поведение относящегося к термины, определения и символы в пункте 1 Он говорит (акцент мой идет вперед):

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

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

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

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

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

и §7.14 обработка сигнала <signal.h> говорит:

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

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

raise(SIGSEGV);

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

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

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

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

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

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

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

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

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

main(){main();}

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

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

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

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

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

The SIGSEGV сигнал может быть отражен, но нет SIGSEGV символ в стандартную библиотеку C++.

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

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

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

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

#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;
}

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

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

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

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

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

*(int*)0 = 0;

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

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

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

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

?

.

 main;

вот и все.

действительно.

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

однако проблема заключается в том, как система запускает исполняемые файлы. В двух словах, стандарт C требует, чтобы все исполняемые файлы C имели окружающая среда-подготовка точки входа, встроенной в них, которая в основном сводится к "вызову 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    xfffffffffffffff0,%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 <__libc_start_main@GLIBC_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's it! 

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