txt[0] = 0x4142;
- это присвоение объекту char
, поэтому правая часть неявно приводится к (char)
после оценки.
Эквивалент NASM равен mov byte [rsp-4], 'BA'
.Сборка этого с помощью NASM выдает то же предупреждение, что и ваш компилятор C:
foo.asm:1: warning: byte data exceeds bounds [-w+number-overflow]
Кроме того, современный C не высокоуровневый ассемблер .C имеет типы, а NASM - нет (размер операнда указывается только для каждой инструкции).Не ожидайте, что C будет работать как NASM.
C определен в терминах «абстрактной машины», и задача компилятора состоит в том, чтобы создать asm для целевого CPU, который выдает такие же наблюдаемые результаты , как если бы C работал непосредственно на абстрактной машине C.Если вы не используете volatile
, фактическое сохранение в память не считается видимым побочным эффектом.Вот почему компиляторы C могут хранить переменные в регистрах.
И, что более важно, вещи, которые имеют неопределенное поведение в соответствии со стандартом ISO C, могут все еще быть неопределенными при компиляции для x86 .Например, x86 asm имеет четко определенное поведение для переполнения со знаком: он оборачивается.Но в C это неопределенное поведение, поэтому компиляторы могут использовать его для создания более эффективного кода для for (int i=0 ; i<=len ;i++) arr[i] *= 2;
, не беспокоясь о том, что i<=len
всегда может быть верным, создавая бесконечный цикл.См. Что должен знать каждый программист на C о неопределенном поведении .
Проникновение типов путем приведения указателя, отличного от char*
или unsigned char*
(или * 1038)* и другие встроенные типы Intel SSE / AVX, поскольку они также определены как may_alias
типы) нарушают правило строгого наложения имен.txt
является массивом символов, но я думаю это все еще строгое нарушение псевдонимов - записать его через uint16_t*
и затем прочитать обратно через txt[0]
и txt[1]
.
Некоторые компиляторы могут определять поведение *(uint16_t*)txt = 0x4142
, или случается для создания кода, который вы ожидаете в некоторых случаях, но вы не должны полагаться на то, что он всегда работает и безопасен, другой код также читает и пишетtxt[]
.
Компиляторы (т. Е. Реализации C, использующие терминологию стандарта ISO) могут определять поведение, которое стандарт C оставляет неопределенным.Но в стремлении к более высокой производительности они решили оставить множество вещей неопределенными. Именно поэтому компиляция C для x86 не похожа на непосредственную запись в asm .
Многие люди считают, что современные компиляторы C активно враждебны программисту, ищаоправдания, чтобы "некомпилировать" ваш код.См. 2-ю половину этого ответа по gcc, строгим псевдонимам и страшным историям , а также комментарии.(Пример в этом ответе безопасен с правильным memcpy
; проблема заключалась в пользовательской реализации memcpy
, которая была скопирована с использованием long*
.)
Вот реальный пример неверно выровненного указателя, приводящего к ошибке на x86 (потому что стратегия автоматической векторизации gcc предполагала, что некоторое целое число элементов достигнет 16-байтовой границы выравнивания.это зависело от выравнивания uint16_t*
.)
Очевидно, что если вы хотите, чтобы ваш C был переносимым (в том числе не для x86), вы должны использовать четко определенные способы ввода слов.В ISO C99 и более поздних версиях запись одного члена союза и чтение другого четко определены.(И в GNU C ++, и в GNU C89).
В ISO C ++ единственный четко определенный способ ввода текста с помощью memcpy
или других char*
обращений для копирования представлений объектов.
Современные компиляторы знают, как оптимизировать memcpy
для небольших постоянных размеров времени компиляции.
#include <string.h>
#include <stdint.h>
void set2bytes_safe(char *p) {
uint16_t src = 0x4142;
memcpy(p, &src, sizeof(src));
}
void set2bytes_alias(char *p) {
*(uint16_t*)p = 0x4142;
}
Обе функции компилируются в один и тот же код с gcc, clang и ICC для x86-64 System V ABI:
# clang++6.0 -O3 -march=sandybridge
set2bytes_safe(char*):
mov word ptr [rdi], 16706
ret
В семействе Sandybridge нет киосков декодирования LCP для 16-битных mov
немедленных, только для 16-битных немедленных сALU инструкции.Это улучшение по сравнению с Nehalem (см. Руководство по микроарху Агнера Фога ), но, очевидно, gcc8.1 -march=sandybridge
не знает об этом, потому что ему все еще нравится:
# gcc and ICC
mov eax, 16706
mov WORD PTR [rdi], ax
ret
определить массив как одну переменную.
... и получить доступ к элементам с помощью ((uint8_t*) &txt)[0]
Да, все в порядке, предполагая, что uint8_t
равно unsigned char
, поскольку char*
разрешено псевдонимом чего угодно.
Это относится практически к любой реализации, которая вообще поддерживает uint8_t
, но теоретически возможно построить такую, где ее нет, а char
16- или 32-битный тип, и uint8_t
реализован с более дорогим чтением / изменением / записью содержащего слова.