Comments 55
memcpy(&value, str, str+strlen(str)+1)); //копируем в нее данные
https://ru.wikipedia.org/wiki/Memcpy
void *memcpy(void *dst, const void *src, size_t n);
где
dst — адрес буфера назначения
srс — адрес источника
n — количество байт для копирования
Так что то, что у вас «все всегда на своих местах» — это результат какого-то невероятного везения :))
Undefined behaviour же классический.
Да чего рассказываю, когда хороша ссылка есть.
void *memcpy(void *dst, const void *src, size_t n);
была использована перегруженная функция:
memcpy(&value, str, str+strlen(str)+1));
Зачем? Да и этом случае копируется вся строка с довеском. В переменную value??? Как это может работать? Сама суть статьи была не оттестирована? Я в замешательстве.
Почему нельзя брать sizeof от структуры с безразмерными строками и почему нельзя класть их в массив?
sizeof не проходит символы строки, ему без разницы, есть там нулевой символ или нет. Размер структуры определяется на стадии компиляции и sizeof разрешается на стадии компиляции. Размер определяется на основании размеров типов полей в структуре (и учёта выравнивания, конечно же), а уж тип у любого поля точно есть, и его размер компилятору точно известен.
Аналогично с массивом — раз размер структуры известен, то ничто не помешает положить её в массив и оперировать с ней в последствии.
Единственное, что нельзя вызывать на таких строках — это функции для работы со строками (в том числе передавать их в printf). Потому что именно они полагаются на наличие терминального символа.
Извините, неверно вас понял. Я не знал, что возможно определение массива без размера как поля структуры. Спасибо за объяснение.
Однако такой код компилируется во всех стандартах, как в C89 и C99, так и в C++14 (проверял через флаг -std). Правда C++ не даёт инициализировать такое поле, говоря, что любая строка слишком длинная для него. Однако можно инициализировать значением по умолчанию. И C++ позволяет брать от такой структуры sizeof (с тем же результатом, как и в C) и при печати читает за границей памяти структуры.
Как я понимаю, это поле просто имеет размер 0. И соответственно, его смещение указывает на конец структуры с поправкой на выравнивание. Может, это можно даже использовать как-то? ) Например, для детекта, была ли включена упаковка для стуктуры.
Но вы правы, не для строк явно.
Для детекта упаковки можно сделать так:
{ struct {uint8_t c1;uint8_t c2;}tmp_st;
typedef char tmp[sizeof(tmp_st)==2? 1:-1 ]; }
Если размер не совпадет с 2 — не скомпилируется и без assert. И не надо что-то выдумывать, просто брать sizeof нужного элемента и делать такой typedef.
Вспомнилось что с помощью структур можно детектить переполнения буферов: буфер помещается в структуру, после буфера магическое число. Если магическое число изменилось — было переполнение. Не 100%, но довольно надежно.
То, что код компилируется, ничего не значит.
Массивы нулевой длины разрешены в C99, но не в С++(любого стандарта).
И в GCC и в Clang это реализовано через расширение языка, например -Wzero-length-array у Clang.
См. код http://coliru.stacked-crooked.com/a/810283a668408e8a
Использовать это понятно как, например принять по сети пакет с данными переменной длины, но с фиксированным заголовком и указанной длиной буфера.
То, что код компилируется, ничего не значит.
Я это понимаю, но понадеялся, что ключ -std
гарантирует использование только стандартизованных возможностей. Однако с ним код компилируется безо всяких предупреждений.
Но вы правы, вместе с ключом -pedantic
пишет таки, что это нестандартная возможность. И это хорошо.
Допустим у нас есть функция, которая принимает в себя указатель. Мы знаем, что в указателе лежит нуль-терминальная строка, а за ней 4-байтное целое.
Вы подобрали пример, в котором лишили компилятор возможности помочь вам с типами данных и работой с ними. Зачем? Возможно, передача структуры с двумя полями (строка и целое) решит проблему? Всякие стандарты вроде MISRA-C уже после void* валерьянку пьют, а после memcpy да и без проверки на успешность выделения памяти так и рыдать и курить начинают.
Совершенно очевидно, что это «одноразовый» код, т.к. его невозможно отлаживать и поддерживать. Так только олимпиадники и начинающие программисты накодить могут.
Довольно тривиальная задача, не так ли? Проверяем на компе (x86), все ОК.
Сейчас всё сломаю:
struct Bar
{
char string[10];
int numeric;
};
//...
Bar bp;
memset(bp.string, 0, 10);
strcpy(bp.string, "123");
bp.numeric = 1234567;
foo(&bp);
#pragma pack(push, 1)
struct Bar
{
char string[10];
int numeric;
};
#pragma pack(pop)
struct bar {
char str[256];
int value;
}
..
void foo(void* data_ptr)
{
struct bar* pbar=(struct bar*) data_ptr; // приведение указателя
printf("%s %d", pbar->str, pbar->value); //выводим данные
}
Тоже волею судеб вынужден в последнее время натыкаться на подобные грабли, портируя код на ARM. Крайне неприятное поведение, в самых неожиданных местах может быть засада. С другой стороны вынуждает поменьше использовать сишное приведение типов и побольше покрывать всё тестами, поэтому пока не понял как к этому относиться, ругаться или хвалить.
int main(int argc, char *argv[]) {
char buf[64];
int magic=123456789;
strcpy(buf, "hello");
memcpy(buf+strlen(buf)+1, (void *)&magic, sizeof(magic));
printf("%s %d\n", buf, *(int*)(buf+strlen(buf)+1));
return 0;
}
int value = (int)(str+strlen(str)+1);
На некоторых процах такое выражение вызовет segmentation fault с большой вероятность.
memcpy(&value, str+strlen(str)+1, sizeof(int)); //копируем в нее данные
совершенно не корректно. Поскольку это потенциальные грабли предполагающие что порядок байт размер int отправителя и получателя данных одинаков.
int бывает разный...
Обращение по не выровненному адресу — популярные грабли при программировании embed. Обычно это вызывает исключение, однако в вашем случае исключения процессора unaligned access были отключены/не предусмотрены процессором. (Кстати правда, а что у вас за чип)?
Таких приведений указателей лучше избегать, однако если очень хочется, то в gcc >= 4.8 есть специальный ключ ''-mno-unaligned-access" — он автоматически генерит код обращения к полям типов с учетом выравнивания:
Обратите внимание на typedef unaligned_int. Он говорит компилятору, что этот тип может размещаться по любому адресу без выравнивания.
typedef int unaligned_int __attribute ((__aligned__(1)));
void foo(unaligned_int *addr)
{
printf ("%d",*addr);
}
Скомпилируем просто
gcc -Wall -O3
foo(int*):
ldr r2, [r0] @ unaligned
movw r1, #:lower16:.LC0
movs r0, #1
movt r1, #:upper16:.LC0
b __printf_chk
.LC0:
.ascii "%d\000"
А теперь с ключем -mno-unaligned-access
gcc -Wall -O3 -mno-unaligned-access:
foo(int*):
push {r4, r5, r6}
mov r4, r0
ldrb r6, [r0, #1] @ zero_extendqisi2
movw r1, #:lower16:.LC0
ldrb r3, [r0] @ zero_extendqisi2
movt r1, #:upper16:.LC0
ldrb r5, [r4, #2] @ zero_extendqisi2
movs r0, #1
ldrb r2, [r4, #3] @ zero_extendqisi2
orr r3, r3, r6, lsl #8
orr r3, r3, r5, lsl #16
orr r2, r3, r2, lsl #24
pop {r4, r5, r6}
b __printf_chk
.LC0:
.ascii "%d\000"
Обратите внимание — компилятор сам нагенерил кода, который вытягивает и собирает int побайтно из не выровненого адреса.
Загружаем на борду с ARM
int16_t выравнен на границу 2 байт, int32_t выравнен на границу 4 байт, это знает даже школьник.
О выравнивании памяти на ARM процессорах на простом примере