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

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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void reverse(char *);

int main() {
  char *str = calloc(1,'\0');
  strcpy(str,"mystring0123456789");
  reverse(str);
  printf("Reverse String is: %s\n",str);
  return 0;
}

void reverse(char *string) {
  char ch, *start, *end;
  int c=0;
  int length = strlen(string);
  start = string;
  end = string;

  while (c < length-1){
    end++;
    c++;
  }
  c=0;

  while(c < length/2){
    ch = *end;
    *end = *start;
    *start = ch;
    start++;
    end--;
    c++;
  }
}

1-й вопрос:

Несмотря на то, что я выделил только 1 байт памяти для указателя char str (calloc(1,'\0')) и скопировал в него 18-байтовую строку mystring0123456789, и это не выдало никакой ошибки, и программа работала нормально без каких-либо SEGFAULT.

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

Программа работала отлично и выдает результат Reverse String is: 9876543210gnirtsym.

2-й вопрос:

Если заменить оператор

strcpy(str,"mystring0123456789");

с

str="mystring0123456789\0";

программа выдает ошибку сегментации, хотя я выделил достаточно памяти для str (malloc(100)).

Почему программа выдает ошибку сегментации?


person Srini    schedule 18.03.2013    source источник
comment
Код с ошибками очень трудно понять.   -  person David Schwartz    schedule 18.03.2013


Ответы (5)


Несмотря на то, что я выделил только 1 байт памяти для указателя char str(calloc(1,'\0')), и я скопировал в него 18-байтовую строку "mystring0123456789", и это не выдало никакой ошибки и программа работала нормально без каких-либо SEGFAULT.

В вашем коде была ошибка — конечно, она не будет делать то, что вы ожидаете. Исправьте ошибку, и тайна исчезнет.

Если заменить инструкцию
strcpy(str,"mystring0123456789");
на
str="mystring0123456789\0";
программа выдает ошибку сегментации, хотя я выделил достаточно памяти для str ( маллок(100)).

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

Вы не можете изменить константу, вот что делает ее константой. Функция strcpy копирует константу в переменную, которую затем можно изменить.

Представьте, если бы вы могли сделать это:

интервал * ч =

Теперь, если бы вы сделали *h = 1;, вы попытались бы изменить эту константу 2 в своем коде, чего вы, конечно, не можете сделать.

Это фактически то, что вы делаете с str="mystring0123456789\0";. Это заставляет str указывать на ту константу в вашем исходном коде, которую вы, конечно же, не можете изменить.

person David Schwartz    schedule 18.03.2013
comment
О какой именно ошибке вы говорите? ‹p›Я знаю, что calloc(1,'\0') неверен. Программа работает нормально, хотя это ошибка. Почему это работает заставило меня задать этот вопрос. Является ли поведение неопределенным? - person Srini; 18.03.2013
comment
@Srini: Вы выходите за пределы допустимого. Это ошибка. Код с ошибками очень трудно понять. Вместо этого просто исправьте ошибку, и тайна исчезнет. (Подождите, пока вы полностью не поймете правильный код, прежде чем даже пытаться понять код с ошибками.) И действительно ли он работает нормально? Как это не делает то, что вы ожидаете, что он будет работать нормально? - person David Schwartz; 18.03.2013
comment
Да, это работает. Это дает мне обратную строку. Вот что заставило меня задуматься, почему это работает? Это не должно работать, но это так. - person Srini; 18.03.2013
comment
@Srini: Верно, это потому, что в нем есть ошибка. Код с ошибками не будет делать то, что вы ожидаете. Решение состоит в том, чтобы исправить ошибку, и тогда тайна исчезнет. Попытка понять код с ошибками очень сложна, и ее лучше приберечь до тех пор, пока вы полностью не поймете код без ошибок, потому что это сильно зависит от тонких деталей, которые вы обычно можете игнорировать. (Например, каковы именно условия для генерации ошибки и как распределитель памяти на самом деле решает, какой размер блока выделить и так далее.) - person David Schwartz; 18.03.2013
comment
Важный урок заключается в следующем: код с ошибками часто не будет делать то, что вы ожидаете, и может быть непредсказуемым и трудным для понимания. Решение состоит в том, чтобы исправить ошибки, и тогда тайна исчезнет. Вы не можете полагаться на то, что код с ошибками даст предсказуемый сбой. - person David Schwartz; 18.03.2013

  1. Нет требования, чтобы он вызывал ошибку сегментации. Все, что происходит, это то, что ваш сломанный код вызывает неопределенное поведение. Если такое поведение не имеет видимого эффекта, это нормально. Если он форматирует жесткий диск и окрашивает экран в синий цвет, это тоже нормально. Оно не определено.
  2. Вы перезаписываете значение указателя адресом строкового литерала, который полностью не использует выделенную память. Затем вы пытаетесь инвертировать строковый литерал, который находится в постоянной памяти, что вызывает ошибку сегментации.
person unwind    schedule 18.03.2013

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

  2. strcpy(str,"mystring0123456789"); копирует данные в то место, куда указывает str. Так получилось, что в этом месте вы можете записывать данные, не вызывая ловушку (на этот раз). Напротив, str="mystring0123456789\0"; изменяет str, чтобы указать на новое место. Место, на которое он указывает, — это место, где хранится "mystring0123456789\0". Это место, скорее всего, является памятью только для чтения, поэтому при попытке записи в подпрограмме reverse вы получите ловушку.

Подробнее о 1:

Когда calloc выделяет память, она просто создает некоторое пространство, которое вам разрешено использовать. Физически присутствует другая память. Вы можете писать в эту другую память, но не должны. Именно так обстоят дела в реальном мире: если вы арендуете номер в отеле, вам разрешено пользоваться этим номером, но вы не можете пользоваться другими номерами, даже если они быть открытым.

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

Еще одно замечание по поводу calloc: вы попросили его выделить место для одной вещи нулевого размера (исходный код '\0' оценивается как нулевой). Итак, вы просите нулевые байты. Различные стандарты (такие как C и Open Unix) могут говорить об этом по-разному, поэтому может случиться так, что когда вы запрашиваете нулевые байты, calloc дает вам один байт. Однако он точно не даст вам столько байтов, сколько вы написали с помощью strcpy.

person Eric Postpischil    schedule 18.03.2013

Похоже, вы пишете программы на языке C, созданные на динамическом языке или, по крайней мере, на языке, выполняющем автоматическую обработку строк. За неимением более формального определения, я нахожу C языком, очень близким к архитектуре машины. То есть вы принимаете множество программных решений. Многие проблемы с вашей программой являются результатом того, что ваш код вызывает неопределенное поведение. У вас возникла ошибка сегментации с помощью strcpy, потому что вы скопировали память в защищенное место; поведение было неопределенным. Принимая во внимание, что назначение вашей фиксированной строки «mystring0123456789\0» было просто назначением этого указателя на str.

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

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

Такой подход к распределению памяти имеет свои преимущества, поэтому C является популярным языком.

person octopusgrabbus    schedule 18.03.2013
comment
Я не получил segfault с помощью strcpy, я получил его, когда моя строка напрямую назначается переменной, как во втором случае. Теперь я чувствую, что поведение непредсказуемо, когда я выделяю меньше памяти, чем требуется, что в моем случае работало нормально, но в целом это непредсказуемо. - person Srini; 18.03.2013
comment
Я не вижу в вашем оригинале, как была определена str. - person octopusgrabbus; 18.03.2013

Другой случай: у вас может быть segfault, когда вы правильно используете память, И ваша куча стала настолько большой, что ваша физическая память не может с ней справиться (без перекрытия со стеком|текстом|данными|bss -> ссылка)

Доказательство: ссылка , раздел Возможная причина №2

person sdd    schedule 29.08.2014