Почему для получения желаемого результата требуется определение элементов массива b?

Недавно я узнал об указателях приведения типов. Итак, я поигрался и сделал этот плохой код:

#include <stdio.h>
int main()
{
   int* a;
    char b[]= "Hye";
    a = (int*)&b;
    *a='B';
    printf("%s\n", b);
    return 0;
}

Этот код дает выход «B».

После дальнейших экспериментов я написал этот код:

#include <stdio.h>
int main()
{
    int* a;
    char b[]= "Hye";
    a = (int*)&b;
    *a='B';
    b[1]='y';
    b[2]='e';
    printf("%s\n", b);
    return 0;
}

Вывод: «Пока».

Я не могу понять, что делает оператор *a='B'; и почему переопределение второго и третьего элементов массива меняет выходные данные?

Я уже использовал такие вещи, как GPT-4o, искал в Google такие вещи, как указатель приведения типов, затем читал статьи и смотрел связанные с ним видео, чтобы получить ответ на свой вопрос. Но я не получил ни одного. И вот я здесь.

«Этот код дает результат «B». Итак, я делаю вывод, что вы используете компьютер с прямым порядком байтов :-) Подсказка: «B» (на компьютере с прямым порядком байтов, ASCII) — это последовательность из 4 байтов 66, 0, 0, 0.

pmg 19.07.2024 17:54

Ага! Я (извините за поздний ответ. Я здесь всего лишь нуб, поэтому мне буквально пришлось гуглить, что означает «little endian»)

Rajesh Paul 19.07.2024 17:58

В этом коде много неопределенного поведения. Некоторые реализации могут привести к нарушению сегментации или, конечно же, дать результаты, отличные от того, что вы видите. Во-первых, нет никакой гарантии, что ваш массив char приемлемо выровнен для указателя int.

Tom Karzes 19.07.2024 17:58

Почти во всех системах int — это четыре байта, а char — один байт. С помощью *a='B'; компилятор преобразует символ 'B' в int и присваивает его четырем байтам, на которые указывает a. В зависимости от вашей системы порядок байтов ваш массив станет либо { 'B', 0, 0, 0 }, либо { 0, 0, 0, 'B' }. Вы, начинающие книги, должны были научить вас, что нулевой терминатор строки равен 0.

Some programmer dude 19.07.2024 17:59

@pmg Спасибо за ответ. Теперь, когда я думаю об этом, я определенно понимаю, что B будет неявно преобразовано в его значение ASCII. Но я до сих пор не могу понять, как целочисленное значение вызовет такое странное поведение.

Rajesh Paul 19.07.2024 18:01

@Someprogrammerdude Ну, спасибо! Я получил ответ. Теперь я понял, что это был глупый вопрос.

Rajesh Paul 19.07.2024 18:03

Не играйте с указателями в «реальном коде» :-)

pmg 19.07.2024 18:05

@pmg Спасибо за совет! Я буду помнить об этом. Я просто хотел убедиться, что понимаю указатели приведения типов.

Rajesh Paul 19.07.2024 18:07

Попробуйте *a = 6650178; в первом примере :-)

pmg 19.07.2024 18:13

@pmg вау! Это печатает «Пока». Но как это именно работает? (Я заметил, что 66 — это значение ASCII для B. Но как «50178» выводит «ye». Я не думаю, что «50178» вместе или по отдельности представляет символы ASCII «y», «e»)

Rajesh Paul 19.07.2024 18:48

Это 66 неудачное совпадение, не обращайте на него внимания, оно никак не влияет на происходящую здесь работу! Попробуйте разобраться в этом сами; Дам подсказку: думайте базово 256.

pmg 19.07.2024 18:56

Еще одна подсказка: десятичное число 6650178 — это шестнадцатеричное число 00657942. И ваша машина имеет прямой порядок байтов.

Paul Lynch 19.07.2024 19:03

поднимите его на ступеньку выше: измените это 6650178 на число, которое заставит вашу программу печатать :-)

pmg 19.07.2024 19:12

@pmg возможно *a=2698554; дам :-)

Rajesh Paul 19.07.2024 20:45

Отлично, @RajeshPaul, поздравляю!

pmg 19.07.2024 20:48

Также спасибо @PaulLynch за то, что помог мне разобраться🙇

Rajesh Paul 19.07.2024 20:48

Спасибо @pmg за помощь! Думаю, я никогда не пойму, как вы можете знать так много и при этом быть такими скромными🙇

Rajesh Paul 19.07.2024 20:50

Просто важное замечание, которое не упоминалось во всех наших играх: важно, чтобы массив b имел 4 или более байтов ("Bye" равен 4 байтам из-за нулевого терминатора строки).

pmg 19.07.2024 20:52

@RajeshPaul Приятно это выяснить. Простой «трюк» pmg — это хороший опыт обучения — хорошее предложение.

Paul Lynch 19.07.2024 21:00

@pmg Спасибо! Я понял, что выделенная память массива переполняется. Но в этом случае откуда printf получает нулевой символ, обозначающий конец печати? (Записывается ли из-за переполнения нулевой символ в адрес памяти, соседний с массивом, и оттуда он считывает нулевой символ?)

Rajesh Paul 19.07.2024 21:04

Число 2698554 — это 58 + 45*256 + 41*256*256 + 0*256*256*256. 58 дает :, 45 дает -, 41 дает ), а 0 дает нулевой терминатор строки, всего 4 байт. Вот почему важно, чтобы стартовая строка была достаточно длинной!

pmg 19.07.2024 21:07

выделенная память массива НЕ переполняется

pmg 19.07.2024 21:13

@pmg Я вижу, что массив имеет размер 4 байта. Так что он не переполняется. Извините за мою ошибку

Rajesh Paul 20.07.2024 07:18
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
23
115
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

int* a и char b[] — это два разных типа, обычно вы нарушаете строгое правило псевдонимов, которое делает поведение этого шаблона доступа неопределенным (6.5, 7).

Как отмечает @TomKarzes, вы можете столкнуться с проблемами, если ваша платформа требует int выравнивания, скажем, по 4 байтам, тогда как char b[], возможно, потребуется выравнивание только по 1 байту. Если вы используете такую ​​платформу, ваша программа может выйти из строя из-за «ошибки шины» или по другим причинам.

Стандарт C гарантирует только то, что int имеет длину не менее 16 бит. На 64-битной платформе обычно это 4 байта, поэтому *a='B'; вероятно записывает в массив 4 байта b. На платформе с прямым порядком байтов (amd64) первая программа выведет «B» {'B', 0, 0, 0}, а вторая программа — «Пока» {'B'¸'y', 'e' 0}, тогда как обе программы будут печатать «» на платформе с прямым порядком байтов {0, 0, 0, 'B'} и {'0, 'y', 'e', 'B'} соответственно.

У вас тоже возникнут проблемы, если вы sizeof b < sizeof(int). sizeof(char) определяется как 1. stdint.h дает вам доступ int к типам известного размера.

Что касается «поскольку b является массивом символов, я думаю, вы в порядке (6.5, 7)»: Нет. *a='B';` обращается к объекту эффективного типа char [4] (b) или объектам эффективного типа char (элементам b). C 2018 6.5 7 говорит, что доступ к нему должен быть возможен только с использованием lvalue совместимого типа, квалифицированной версии совместимого типа, соответствующего знакового или беззнакового типа, квалифицированной версии соответствующего знакового или беззнакового типа, агрегата или объединения, содержащего один предыдущего или типа символа. Но используемое lvalue имеет тип int, который не является ни одним из них.

Eric Postpischil 19.07.2024 20:21

Re «Если вы работаете на такой платформе, ваша программа может выйти из строя из-за «ошибки шины»»: она также может выйти из строя по другим причинам.

Eric Postpischil 19.07.2024 20:22

Re: «Стандарт C гарантирует только то, что sizeof(int) >= 2»: Стандарт C гарантирует int 16 бит. sizeof измеряет размер в единицах char объектов, а стандарт не требует, чтобы int было как минимум два char. char может быть шире восьми бит. sizeof (int) может быть один.

Eric Postpischil 19.07.2024 20:25

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

Allan Wind 20.07.2024 05:26

@AllanWind - 6.5 7, наверное, мой самый любимый вариант того, как НЕ объяснять что-то в стандарте. «... lvalue совместимого типа, квалифицированная версия совместимого типа, соответствующий знаковый или беззнаковый тип, квалифицированная версия соответствующего знакового или беззнакового типа...» не может быть переварено за один раз. сидя.

David C. Rankin 20.07.2024 07:29

@DavidC.Rankin Как новичок, я с тобой согласен! Мне пришлось прочитать много статей, чтобы понять, о чем он говорит.

Rajesh Paul 20.07.2024 14:10

@DavidC.Rankin Да, простым смертным трудно понять (включая меня; с другой стороны, авторы компиляторов не простые смертные). Мне больше всего нравится то, что мы называем это строгим псевдонимом, хотя термин «псевдоним» встречается только в сноске, а в индексном списке «псевдоним» для этой страницы. Вы даже не можете найти этот термин.

Allan Wind 20.07.2024 22:03

@RajeshPaul - не расстраивайтесь, этот раздел стандарта (в широком смысле называемый «Правилом строгого псевдонимов») также был предметом многочисленных споров и дискуссий среди старожилов. Суть правила заключается в том, чтобы не обращаться к вещам в памяти через указатели другого типа. Вся эта тарабарщина — попытка объяснить исключения из того, когда это допустимо. (существуют законные варианты использования исключений, например, для функций, которые возвращают типы void, не имеющие «типа», например memcpy() и т. д.) Если вы всегда используете правильный указатель типа, вы обычно не столкнетесь с этой проблемой.

David C. Rankin 21.07.2024 10:21

Другие вопросы по теме