LINUX.ORG.RU

C with C++, static non-const, void* casts, segfault

 , ,


0

2

А есть ли какие-нибудь отличия в поведении C и C++ при кастовании char* в void* и обратно?

А то я тут пишу плюсявый плагин к сявой софтине: из неё вызывается мой плюсявый callback f():

extern "C" void f() {
    static const char msg[] = "xxx";  
    g(msg, sizeof(msg) - 1);
}

void g(const char* data, size_t size) {
     сявая_структура x;
     x.data = data;
     x.size = size;
     сявый_вызов(x);
}

Если я уберу const с объявления msg внутри f(), и объявлю параметр data функции g() как const void*, а внутри напишу x.data = (char*)data, то всё сегфолтится. Почему?

★★★★★

Последнее исправление: dimgel (всего исправлений: 2)

const-объекты не могут быть изменены. Если ты получил const-ссылку, сделал const_cast¹, чтобы избавиться от const-ности, и поменял объект, это UB. Так можно делать, только если знаешь, что исходный объект, на который ссылка - действительно не const.

Поэтому компиляторы на практике по возможности располагают статические const данные в read-only секции, чтобы твоя программа при попытке их поменять вовсе упала. Это и произошло.

Обычно данные в callback передают через void*. Тот, кто установил callback, может проконтролировать и const-ность объекта, на который передаётся указатель², и то, что callback не меняет его, поэтому const_cast на границе между пользовательским и библиотечным кодом - ОК, но надо быть осторожным.

Эти правила действуют одинаково и в C++, и в C (с точностью до наличия в языке const_cast).

¹ (в C++ сишный каст с круглыми скобками тоже первым делом выполняет const_cast)

² (а может быть, это вообще не указатель, а просто упакованное в void* число)

lwo
()
Последнее исправление: lwo (всего исправлений: 1)
Ответ на: комментарий от dimgel

Рекомендую valgrind. Легко выявляет места нарушений памяти с точностью до функции. А если сочетать его с отладочным выводом в std::cerr, то и с точностью до строки получается.

hobbit ★★★★★
()
Ответ на: комментарий от emorozov

За инфу спасибо, но пересобирать её всю с отладочной инфой мне в данный момент нифига не улыбается. Я думал кто-нить чисто по описанию проблемы тыкнет мне где я не прав.

dimgel ★★★★★
() автор топика
Ответ на: комментарий от lwo

Поэтому компиляторы на практике по возможности располагают статические const данные в read-only секции, чтобы твоя программа при попытке их поменять вовсе упала. Это и произошло.

Не, я снимаю const внутри g() исключительно потому что C не умеет в const. Сам сявый API-вызов эти данные не меняет. Кстати, даже если я этот API-вызов убирал, и вместо этого возвращал x из g(), всё равно падало. А если вручную инлайнил g() в f(), то не падало.

И кстати, падало как раз когда msg был не const.

Вот такие, панимаеш ли, чудеса. Никаких мыслей, кроме разве что что баг вообще в другом месте, а мои эксперименты косвенно его индуцируют, влияя на работу оптимизатора. Но я много попутно и в других местах правил, а тем не менее поведение сохраняется как я описал в ТС.

dimgel ★★★★★
() автор топика
Последнее исправление: dimgel (всего исправлений: 2)
Ответ на: комментарий от dimgel

Если ты снимаешь const и модифицируешь объект, а он на самом деле был объявлен как const - у тебя UB, и на практике твоя программа упадёт. Ещё раз, компилятор увидел const char msg[] и положил строку в .rodata.

На базовом уровне «сохранять поверхностную const-корректность, пока не просят обратного» C вполне умеет в const.

lwo
()
Ответ на: комментарий от dimgel

Возможно, что даже без отладочной информации что-то прояснится. Ну и честно говоря странно, что нет возможности собрать с отладочной инфой, или что по умолчанию собирается без неё, а не при релизе.

emorozov
()

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

Наверняка это сделает проще жизнь тем, кто попробует вам помочь.

eao197 ★★★★★
()

А есть ли какие-нибудь отличия в поведении C и C++ при кастовании char* в void* и обратно?

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

тут похоже стремное место, что это вообще?

     сявый_вызов(x);

какой тут прототип функции?

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от dimgel

За инфу спасибо, но пересобирать её всю с отладочной инфой мне в данный момент нифига не улыбается.

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

annulen ★★★★★
()
Последнее исправление: annulen (всего исправлений: 1)

само такое преобразование не может вызывать сегфолт.
Все отлично работает.
Значит внутри твоего вызова каким то образом делается какое то непотребство.

апд. Возможно у тебя там перегрузка этого вызова для конст и не конст аргумента?

safocl ★★
()
Последнее исправление: safocl (всего исправлений: 2)