Pull to refresh

Comments 22

Напоследок — небольшое замечание по поводу объявления типов, которые уже определены в системных файлах.
V677 Custom declaration of a standard 'BYTE' type. The system header file should be used: #include <WinDef.h>. des.h 15
Конечно, это не ошибка. Но зачем так делать?

Затем, чтобы не зависеть от системных объявлений? На иных платформах этот тип может быть не объявлен вовсе или объявлен иначе.
В данном случае подозреваю, что этот BYTE — исключительно внутренний, не имеющий никакого отношения к системному BYTE (в том смысле, что экземпляры внутреннего BYTE никогда не передаются в функции, ожидающие системный BYTE).
Ну так и не называйте его BYTE. Зачем переопределять системные объекты?
А он и не переопределяется — он в другой области видимости.
И почему не называть? Семантически в данной реализации он представляет собой байт. Имя отражает назначение — всё нормально.
Зачем называть системные объекты не так, как у людей?

Можно сказать, что BYTE появился гораздо раньше, чем uint8_t, но и ведь и NSS писался не под Windows изначально…
V677 Custom declaration of a standard 'BYTE' type.

В каком таком стандарте описан тип BYTE?

RtlSecureZeroMemory() function should be used

Как бальзам на душу разработчикам кроссплатформенной библиотеки.

Не проще ли так?
static inline void *memset_safe(void *s, int c, size_t n) {
  void *ret = memset(s, 0, n);
  *(volatile char*)s= *(volatile char*)s;
  return ret;
}
В каком таком стандарте описан тип BYTE?
ECMA-234, однако. Хотя кому он интересен…
Вы уверены, что LTO не выкинет такой вызов memset? В LibreSSL было много проблем именно с LTO.
Не уверен, мне вообще хотелось бы увидеть адекватные комментарии по этому вопросу вместо совета использовать RtlSecureZeroMemory.
Не раз уже обращали на это внимание. Может поможет этот комент.
Для Win* совет правильный.
Переменная 'j' дважды используется в одной точке следования. В результате невозможно предсказать результат работы такого выражения. Подробнее в описании диагностики V567.

Я категорически не понимаю, в чём здесь проблема. Например, код, который инициализирует массив числами от 0 до 9, тоже некорректный?
for (int i = 0; i < 10; a[i] = i++);


В описании диагностики V567 хорошо написано, почему нельзя делать i_acc = (++i_acc) % N_acc, и это понятно.
Но для примера a[i] = i++ можно привести ошибочный псевдокод, который теоретически может быть создан компилятором?
Первый вариант:
registerA = i;
registerB = a;
registerB += registerA;
registerC = i;
i = i + 1;
*registerB = registerC;

Второй вариант:
registerC = i;
i = i + 1;
registerA = i;
registerB = a;
registerB += registerA;
*registerB = registerC;

Как-то так. Неизвестно, будет в начале увеличена переменная 'i' или посчитан адрес ячейки памяти, куда следует положить результат.
Могу быть неправ. Прошу знатоков меня поправить. Но в любом случае, так делать нельзя.
Это кстати есть в Frequently Reported Bugs GCC. Если кратко — всё, что происходит между точками следования, может происходить в произвольном порядке на усмотрение компилятора. Просто компиляторы научились предугадывать такие финты программистов, поэтому если отбросить экзотику, остаются только gcc, clang и VS, которые старательно избегают подобного рода багов в программах — в противном случае общественность обвинит компилятор в том, что «нормальные» программы в нём не собираются.
По ссылке примеры с пре-инкрементом, а не пост-инкрементом.
Просто потому что пост-инкремент ведёт себя в понимании программистов «правильно», но если поменять его на пре-инкремент, большинство удивляется результату. То что компилятор генерирует правильный код ещё не означает, что это не UB. Хотя конечно компилятор мог бы кинуть хотя бы предупреждение, gcc молчит как партизан, зато clang выдаёт:

test.c:4:29: warning: unsequenced modification and access to 'i' [-Wunsequenced]
        for(i = 0; i < 10; a[i] = i++);

Если вас и компилятор не убедит, то мне и подавно никто не поверит.
Теперь убедительно. Я просто запомню, что конструкция i++ обозначает увеличение i в любой удобный момент после доступа к переменной. А не строго после выполнения всей точки следования, как я раньше предполагал.
Тут (как и во многих других местах в C) всё сделано для удобства компилятора. Скажем на процессоре ARM команда
str r0,[r1],#4
возьмёт значение r1, положит его по этому адресу r0, а потом его увеличит не 4. А в x86 такой удобной команды нет и, соответственно, увеличение i, скорее всего, отодвинется куда-нибудь в конец всего выражения. Компилятор тут имеет полную свободу — это задача программиста написать всё так, чтобы в любом случае всё было правильно.

В случае с вашим примером и реально существующими компиляторами, действительно, сложно придумать что-то по настоящему ужасное за исключением случая когда компилятор просто выкинет код «за ненадобностью», но можно придумать какие-нибудь другие варианты где это может реально быть проблемой.
В x86 есть всякие lodsd, stosb, movsw, или cmpsb.
Другое дело, что они считаются легаси и компиляторами не используются (думаю, они по тактам сильно проигрывают последовательности более распространённых инструкций).
Если имеет смысл, то используются:

$ cat test.c 
int foo(char *p, const char *q, int i) {
  extern void *memcpy(void *, const void *, long);
  memcpy(p, q, i);
}

$ gcc -Os -S test.c -o-
	.file	"test.c"
	.section	.text.unlikely,"ax",@progbits
.LCOLDB0:
	.text
.LHOTB0:
	.globl	foo
	.type	foo, @function
foo:
.LFB0:
	.cfi_startproc
	movl	%edx, %ecx
	rep; movsb
	ret
	.cfi_endproc
.LFE0:
	.size	foo, .-foo
	.section	.text.unlikely
.LCOLDE0:
	.text
.LHOTE0:
	.ident	"GCC: (GNU) 4.9.1"
	.section	.note.GNU-stack,"",@progbits
Инструкция rep mosvb (именно так, с префиксом) я думаю декодируется совершенно не так, как movsb, и этот случай отдельно оптимизирован в современных x86 :)
Тут ключевой момент — не rep, а -Os, на самом деле. А если оптимизировать под скорость исполнения, то это зависит от CPU, как я уже писал. Если вы оптимизируете под Pentium4, то movsb может быть использована и без -Os.
Sign up to leave a comment.