Вопрос об объединении в C - хранит как один тип и читал, поскольку другой - является этим определенная реализация?

Одно общее предложение, связанное с исходной темой - используйте TryParse(), как только Вы не действительно уверенный, что параметр входной строки будет корректным представлением числового формата.

29
задан ololuki 24 September 2018 в 19:21
поделиться

6 ответов

Это неопределенное поведение. ui и u.ch расположены по одному и тому же адресу памяти. Таким образом, результат записи в один и чтения из другого зависит от компилятора, платформы, архитектуры и иногда даже от уровня оптимизации компилятора. Поэтому вывод для ui не всегда может быть 515 .

Пример

Например, gcc на моем компьютере дает два разных ответа для -O0 и -O2 .

  1. Поскольку моя машина имеет 32-битную архитектуру с прямым порядком байтов, с -O0 я получаю два младших байта, инициализированных как 2 и 3, два старших байта не инициализируются. Таким образом, память объединения выглядит так: {3, 2, мусор, мусор}

    Следовательно, я получаю результат, аналогичный 3 2 -1216937469 .

  2. С -O2 , я получаю результат 3 2 515 , как и вы, что делает объединенную память {3, 2, 0, 0} . Что происходит, так это то, что gcc оптимизирует вызов printf с фактическими значениями, поэтому вывод сборки выглядит как эквивалент:

     #include  два старших байта не инициализированы. Таким образом, память объединения выглядит так:  {3, 2, мусор, мусор} 

    Следовательно, я получаю результат, аналогичный 3 2 -1216937469 .

  3. С -O2 , я получаю результат 3 2 515 , как и вы, что делает объединенную память {3, 2, 0, 0} . Что происходит, так это то, что gcc оптимизирует вызов printf с фактическими значениями, поэтому вывод сборки выглядит как эквивалент:

     #include  два старших байта не инициализированы. Таким образом, память объединения выглядит так:  {3, 2, мусор, мусор} 

    Следовательно, я получаю результат, аналогичный 3 2 -1216937469 .

  4. С -O2 , я получаю результат 3 2 515 , как и вы, что делает объединенную память {3, 2, 0, 0} . Что происходит, так это то, что gcc оптимизирует вызов printf с фактическими значениями, поэтому вывод сборки выглядит как эквивалент:

     #include  0} . Что происходит, так это то, что  gcc  оптимизирует вызов  printf  с фактическими значениями, поэтому вывод сборки выглядит как эквивалент: 

     #include  0} . Что происходит, так это то, что  gcc  оптимизирует вызов  printf  с фактическими значениями, поэтому вывод сборки выглядит как эквивалент: 

     #include 
    int main () {
     printf ("% d% d% d \ n", 3, 2, 515);
     возврат 0;
    }
    

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

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

23
ответ дан 28 November 2019 в 01:20
поделиться

Причина вывода состоит в том, что на вашей машине целые числа хранятся в формате little-endian : младшие байты сохраняются первыми. Следовательно, последовательность байтов [3,2,0,0] представляет собой целое число 3 + 2 * 256 = 515.

Этот результат зависит от конкретной реализации и платформы.

9
ответ дан 28 November 2019 в 01:20
поделиться

The answer to this question depends on the historical context, since the specification of the language changed with time. And this matter happens to be the one affected by the changes.

You said that you were reading K&R. The latest edition of that book (as of now), describes the first standardized version of C language - C89/90. In that version of C language writing one member of union and reading another member is undefined behavior. Not implementation defined (which is a different thing), but undefined behavior. The relevant portion of the language standard in this case is 6.5/7.

Now, at some later point in evolution of C (C99 version of language specification with Technical Corrigendum 3 applied) it suddenly became legal to use union for type punning, i.e. to write one member of the union and then read another.

Note that attempting to do that can still lead to undefined behavior. If the value you read happens to be invalid (so called "trap representation") for the type you read it through, then the behavior is still undefined. Otherwise, the value you read is implementation defined.

Your specific example is relatively safe for type punning from int to char[2] array. It is always legal in C language to reinterpret the content of any object as a char array (again, 6.5/7).

However, the reverse is not true. Writing data into the char[2] array member of your union and then reading it as an int can potentially create a trap representation and lead to undefined behavior. The potential danger exists even if your char array has sufficient length to cover the entire int.

But in your specific case, if int happens to be larger than char[2], the int you read will cover uninitialized area beyond the end of the array, which again leads to undefined behavior.

17
ответ дан 28 November 2019 в 01:20
поделиться

Вывод такого кода будет зависеть от вашей платформы и реализации компилятора C. Ваш вывод заставляет меня думать, что вы запускаете этот код в системе с прямым порядком байтов (вероятно, x86). Если вы поместите 515 в i и посмотрите на него в отладчике, вы увидите, что младший байт будет 3, а следующий байт в памяти будет 2, что точно соответствует тому, что вы поместили в ch.

Если вы сделали это в системе с прямым порядком байтов,

5
ответ дан 28 November 2019 в 01:20
поделиться

Это зависит от реализации, и результаты могут отличаться в зависимости от платформы / компилятора, но, похоже, именно это и происходит:

515 в двоичном формате - это

1000000011

Заполнение нулями, чтобы сделать его двумя байтами (при условии 16-битного int):

0000001000000011

Два байта:

00000010 and 00000011

Это 2 и 3

Надеюсь, кто-то объяснит, почему они поменялись местами - я предполагаю, что символы не обратное, но int является прямым порядком байтов.

Объем памяти, выделенной для объединения, равен памяти, необходимой для хранения самого большого члена. В этом случае у вас есть массив int и char длины 2. Предполагая, что int - 16 бит, а char - 8 бит, оба требуют одинакового пространства, и, следовательно, объединению выделяется два байта.

Когда вы назначаете три (00000011) и два (00000010) в массив char, состояние объединения будет 0000001100000010 . Когда вы читаете int из этого объединения, он преобразует все в целое число. Предполагая представление little-endian , где LSB хранится по наименьшему адресу, int, считываемый из объединения, будет 0000001000000011 , который является двоичным для 515.

ПРИМЕЧАНИЕ. Это верно даже если int было 32-битным - проверьте ответ Амнона

5
ответ дан 28 November 2019 в 01:20
поделиться

Если вы работаете в 32-битной системе, тогда int составляет 4 байта, но вы инициализируете только 2 байта. Доступ к неинициализированным данным является неопределенным поведением.

Предполагая, что вы работаете в системе с 16-битными целыми числами, то, что вы делаете, все еще определяется реализацией. Если в вашей системе обратный порядок байтов, то u.ch [0] будет соответствовать младшему значащему байту ui, а u.ch 1 будет самым старшим значащим байтом. В системе с прямым порядком байтов все наоборот. Кроме того, стандарт C не заставляет реализацию использовать дополнение до двух для представления целочисленных значений со знаком, хотя дополнение до двух является наиболее распространенным. Очевидно, что размер целого числа также определяется реализацией.

Подсказка: легче увидеть, что происходит, если вы используете шестнадцатеричные значения.

2
ответ дан 28 November 2019 в 01:20
поделиться
Другие вопросы по тегам:

Похожие вопросы: