2010-01-18 6 views
4

Я наткнулся на это на канале IRC вчера и не понял, почему это было плохое поведение:Что не так с этим С броском

#include <stdio.h> 

int main(void) 
{ 
    char x[sizeof(int)] = { '\0' }; int *y = (int *) x; 
    printf("%d\n", *y); 
} 

Есть ли потеря данных или что-нибудь? Может ли кто-нибудь дать мне какие-либо документы, чтобы объяснить далее, что он делает неправильно?

+1

Имеет ли это значение? Зачем вам писать такой код? – Clifford

+9

@Cliff, зависит от вашего определения вопросов. Если знать больше о языке, в том числе в углах, это хорошо, это важно. Знание всегда хорошее. – GManNickG

+2

@Clifford - даже если вы не напишете этот код, вы можете когда-нибудь понадобиться поддерживать чужой код, написанный таким образом. –

ответ

7

Во-первых, массив x не может быть правильно выровнен для int.

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

То же самое не относится к памяти, выделенной компилятором для автоматических переменных.

+0

Есть ли разница в правилах между C и C++? Это действительно в C++. (Так же, как 'placement new',' int * y = new (x) int; ') – GManNickG

+2

Я не думаю, что это действительно C++. Соответствующие типы не удовлетворяют 3.10/15. В частности, поведение кода зависит от выравнивания 'int' и от представления значения' int'. В C++ это еще больше проблема, потому что C++ заявляет, что неопределенное поведение вводит недетерминированность в его абстрактную машину, и если * любой * таких возможных путей выполнения дает неопределенное поведение, то у него нет требований к исполняемой реализации. Это означает, что приведенный выше код эффективно выполняет неопределенное поведение, поскольку представление значения и объекта не определено. –

+0

C++ - FAQ сам даже использует почти такой же код: http://www.parashift.com/c++-faq-lite/dtors.html#faq-11.10 Я знаю, что C++ - FAQ - это не последнее слово о вещах, но я не думаю, что автор пропустит нечто подобное. EDIT: Но теперь, когда лампочка зашифровала ...: P – GManNickG

10

Массив x может быть неправильно выровнен в памяти для int. На x86 вы не заметите, но на других архитектурах, таких как SPARC, разыменование y вызовет ошибку шины (SIGBUS) и сбой вашей программы.

Эта проблема может возникнуть по адресу:

int main(void) 
{ 
    short a = 1; 
    char b = 2; 

    /* y not aligned */ 
    int* y = (int *)(&b); 
    printf("%d\n", *y); /* SIGBUS */ 
} 
+2

Вы бросаете символ в указатель, так что очевидно, что он терпит неудачу. Хотя его массив имеет тот же размер, что и int, и его литье в int. – qba

+0

@qba - проблема выравнивания, а не размера. Хорошая дискуссия об этом после ответа Майкла Берра. –

+0

Возможно, было бы лучше, если бы вы сделали 'b'' 'int'. – GManNickG

0

Почему бы не использовать союз вместо этого?

union xy { 
    int y; 
    char x[sizeof(int)]; 
}; 
union xy xyvar = { .x = { 0 } }; 
... 
printf("%d\n", xyvar.y); 

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

+0

Это не" t, поскольку стандарт говорит о том, что запись одного члена союза, а затем чтение другого не переносится. OTOH, если вы собираетесь вообще писать, это * about *, как переносимый, как он получает. –

+0

Итак, это не * официально * портативный, но практически * переносимый. для какой-то установки, где она не сработает (игнорируя очевидные проблемы с контентом). –

+0

@Jerry, если 'x' был типа' unsigned char', тогда он будет определен и переносимым, не так ли? –

0

Я думаю, что, хотя проблема выравнивания верна, это не вся история. Даже если выравнивание не является проблемой, вы все равно принимаете 4 байта в стеке, только один из них инициализируется до нуля и обрабатывает их как целое. Это означает, что напечатанное значение имеет 24 не инициализированных бита. И использование неинициализированных значений является основным «неправильным».

(Предполагая sizeof (int) == 4 для простоты).

+0

Нет, все элементы массива 'x' инициализируются равными 0. В C, если у агрегатного типа меньше инициализаторов, чем необходимо, они все остальные считаются 0. –

Смежные вопросы