В этом вопросе я имею в виду другой вопрос stackoverflow, потому что у меня недостаточно баллов, чтобы прокомментировать его.
См.: Valgrind сообщает о недействительном Realloc
Вкратце: почему инструкция buffer = trim(buffer);
должна быть написана вне функции? Почему это нельзя записать внутри функции как phrase = (char*)realloc(phrase, strlen(phrase)+1);
?
Подробно: если я передам любой указатель на функцию, например const *str;
, то, выполнив str++;
, я смогу создать побочный эффект и изменить начальную позицию строки.
Но почему я не могу просто переназначить другое значение функции с помощью функций управления динамической памятью, таких как malloc
и realloc
?
Почему я не могу просто поставить str = (char*) realloc(str, strlen(str) + 1 * sizeof(char));
?
Чем этот побочный эффект отличается от другого?
Разве я не могу гипотетически просто двигаться str
куда захочу?
@AviBerger Но если это «передача по значению», почему я могу создавать эти побочные эффекты и просто увеличивать их (извините, я пришел из программирования на Java)
Аргумент phrase
в trim
строго локален для этой функции. Звонящий не видит. Любые изменения phrase
изнутри trim
будут скрыты от вызывающего абонента. Вот почему trim
возвращает новое значение указателя. Если бы это было не так, оно было бы потеряно, когда trim
вернется.
Вы можете изменить копию. Это не имеет прямого отношения к оригиналу. Java отличается от C. У меня есть для вас мантра программирования на C: «Нет такой вещи, как ссылочный тип. Нет такой вещи, как ссылочный тип».
realloc — это функция, освобождающая память, на которую указывает ptr. Если освобождаемая память является памятью стека, а не управляемой памятью, realloc не работает. Если вы хотите просто КОПИРОВАТЬ память str, используйте другую функцию.
@AviBerger @TomKarez @david Большое спасибо за вашу помощь, ребята! Извините, но мне все еще очень трудно обдумать это. Если это строго локальная копия - то почему я могу изменить ее операцией инкремента, чтобы мои изменения были видны извне? например: C static void sideEffect(char *str) { str++; // works fine! This instruction changes the actual value - not it's copy! str = (char*) malloc(strlen(str) + 1 * sizeof(char)); // doesn't work for some reason :( }
@sueszli: str
— это копия любого значения, которое было передано функции sideEffect
. Поэтому str++
изменяет значение локальной копии, а не значение исходной переменной, которая использовалась для вызова функции sideEffect
. То же самое относится и к str = (char*) malloc(strlen(str) + 1 * sizeof(char));
.
Java также передается по значению. Передайте int
функции, и если вы измените это значение, вызывающая сторона не увидит изменений. void test(char **str) { (*str)++; }
тогда предположим char *fubar = "hello world"; test(&fubar);
... поэкспериментируйте с этим. Кроме того, K&R2e. Читай, делай упражнения по мере того, как натыкаешься на них...
Я думаю, что, возможно, часть путаницы заключается в том, что мы видим два разных оператора присваивания: ++
и =
и ожидаем, что один из них изменит мир вызывающего абонента, а другой нет...
В Java у вас есть переменная, которая связана с динамически выделяемым объектом. В C вы имеете дело непосредственно с сущностями, которые не обязательно распределяются динамически. Вы можете (но не всегда) иметь дескрипторы этих сущностей. Это собственные объекты, классифицируемые как указатели. В sideEffect
str++ изменяет локальный str (указатель). Это не влияет на сущность, которая была передана в fcn. Он также не изменяет буфер, на который указывает, а только место в этом буфере, на которое он указывает.
@sueszli: Эта демонстрационная программа может помочь вам понять. Как видите, значение исходной переменной сохраняет свое значение, изменяется только копия.
@autistic Woah, вы правы — мое первоначальное предположение, что я могу даже создать побочный эффект с помощью str++
, было неверным. Я попробовал еще раз и понял, что - Спасибо за объяснение.
@AviBerger - Этот контраст с Java действительно заставил меня понять различия. Большое спасибо - теперь я действительно понял это :)
@AndreasWenzel Не знал, что существует компилятор браузера! Спасибо за демонстрацию. Действительно полезно!
@sueszli мы можем объявить наши аргументы T *fubar
вместо T fubar
, а затем оперировать (*fubar)
, а не fubar
. В вашем случае T
— это char *
, поэтому T *
— это char **
. Мы можем использовать одну и ту же идиому для любого типа, чтобы вызывающий мог видеть обновления, которые мы вызываем. realloc
вместо этого передает эту информацию через возвращаемое значение (как указано в ответе). Я решил объяснить это, чтобы прояснить ваши убеждения о передаче по значению, поскольку вы, похоже, смущены этим. Как и в Java, необходимо передать ссылочный тип (конечно, по значению), чтобы вызывающая сторона могла видеть обновления того, на что указывает аргумент.
C передает все аргументы по значению. Посмотрите на параметры вызываемой функции, как на локальные переменные этой функции, инициализированные копиями аргументов, переданных вызывающей стороной.
Мы можем «эмулировать» передачу по ссылке, если передаем указатель. Разыменовав указатель, мы можем получить доступ к «ссылочному» объекту, находящемуся вне вызываемой функции. Но этот указатель по-прежнему передается по значению, то есть является копией аргумента, инициализирующего параметр.
Примечание. Ссылки на C++ и другие языки не что иное, как указатели внутри. Однако есть дополнительная семантика. Возможно, вы захотите посмотреть на сгенерированный машинный код.
Таким образом, вы можете делать все, что хотите, с этим указателем в параметре, перезаписывать его, увеличивать или уменьшать его, даже NULL
. Это не влияет на источник указателя в вызывающем объекте.
Проблема вопроса, на который вы ссылаетесь, может быть сведена к следующему:
char* called(char* pointer)
{
return realloc(pointer, /* some irrelevant value */);
}
void caller(void)
{
char* buffer = malloc(/* some irrelevant value */);
/* ignore returned pointer */ called(buffer);
free(buffer); /* here Valgrind reports an error */
}
Нам нужно различать несколько случаев для realloc()
здесь.
realloc()
возвращает NULL
, так как для выполнения запроса недостаточно памяти. Прежний адрес по-прежнему действителен.realloc()
возвращает тот же адрес, потому что таким образом он может удовлетворить запрос. Поскольку возвращенный и прежний адреса равны, оба действительны.realloc()
возвращает новый адрес. Прежний адрес теперь недействителен.Из них третий случай является общим и приводит к задокументированной проблеме.
Поскольку buffer
в caller()
не изменяется called()
, просто потому, что called()
не может получить к нему доступ, он по-прежнему содержит прежний адрес. Теперь, когда free()
вызывается с этим недопустимым адресом, обнаруживается ошибка.
Чтобы исправить эту ошибку, caller()
необходимо использовать возвращаемое значение. Правильный способTM сделать это:
void caller(void)
{
char* buffer = malloc(/* some irrelevant value */);
char* new_buffer = called(buffer);
if (new_buffer != NULL) {
buffer = new_buffer;
} else {
/* handle the re-allocation error, the address in buffer is still valid */
}
free(buffer);
}
Альтернативой является передача указателя на buffer
в called()
и изменение buffer
корректно. Но этот тип перенаправления часто генерирует хуже читаемый код. Однако для удобства вы можете решить пойти по этому пути.
Вау, большое вам спасибо - это был такой красноречивый ответ, и он ДЕЙСТВИТЕЛЬНО ДЕЙСТВИТЕЛЬНО помог мне понять, почему realloc ведет себя так, а не иначе, и почему моя ментальная модель была ошибочной.
C передается по значению. Функция не имеет доступа к переменной char * вызывающего объекта, а только к ее копии. Вы можете изменить значение указателя (надеюсь, на действительное новое значение), но для realloc требуется значение, которое было возвращено malloc/calloc/realloc, а не просто то, которое куда-то указывает, даже не внутри динамически выделенного блока.