Я создаю библиотеку строк для поддержки как ascii, так и utf8.
Я создаю два typedef для t_ascii и t_utf8. ascii безопасно читать как utf8, но utf8 небезопасно читать как ascii.
Есть ли способ выдать предупреждение при неявном приведении от t_utf8 к t_ascii, но не при неявном приведении t_ascii к t_utf8?
В идеале я бы хотел, чтобы эти предупреждения (и только эти предупреждения) выдавались:
#include <stdint.h>
typedef char t_ascii;
typedef uint_least8_t t_utf8;
int main()
{
t_ascii const* asciistr = "Hello world"; // Ok
t_utf8 const* utf8str = "你好世界"; // Ok
asciistr = utf8str; // Warning: utf8 to ascii is not safe
utf8str = asciistr; // Ok: ascii to utf8 is safe
t_ascii asciichar = 'A';
t_utf8 utf8char = 'B';
asciichar = utf8char; // Warning: utf8 to ascii is not safe
utf8char = asciichar; // Ok: ascii to utf8 is safe
}
В настоящее время при сборке с -Wall (и даже с -funsigned-char) я получаю следующие предупреждения:
gcc main.c -Wall -Wextra
main.c: In function ‘main’:
main.c:10:35: warning: pointer targets in initialization of ‘const t_utf8 *’ {aka ‘const unsigned char *’} from ‘char *’ differ in signedness [-Wpointer-sign]
10 | t_utf8 const* utf8str = "你好世界"; // Ok
| ^~~~~~~~~~
main.c:12:18: warning: pointer targets in assignment from ‘const t_utf8 *’ {aka ‘const unsigned char *’} to ‘const t_ascii *’ {aka ‘const char *’} differ in signedness [-Wpointer-sign]
12 | asciistr = utf8str; // Warning: utf8 to ascii is not safe
| ^
main.c:16:17: warning: pointer targets in assignment from ‘const t_ascii *’ {aka ‘const char *’} to ‘const t_utf8 *’ {aka ‘const unsigned char *’} differ in signedness [-Wpointer-sign]
16 | utf8str = asciistr; // Ok: ascii to utf8 is safe
| ^
Я думаю, вы должны обернуть их в непрозрачные структуры, которые будут отличаться от системы типов C, даже если они оба обертывают один и тот же базовый char *. Возможно (и программисты на C со мной не согласятся, я знаю), что у вас нет не строковых типов, а указателей на символы.
На самом деле, я наблюдаю предупреждение: warning: assigning to 'const t_ascii *' (aka 'const char *') from 'const t_utf8 *' (aka 'const signed char *'). Обратите внимание, что обычный char имеет «знаковость», которая не является ни подписанной, ни беззнаковой.
Вы задаете вопрос на самом деле о приведении собственных типов, а не (как в приведенных вами примерах) о приведении указатели к этим типам? Разные вещи. Очень разные, правда.
Единственное асимметричное предупреждение, о котором я знаю, - это преобразование между объектами разных размеров, например. присваивая uint8_tuint16_tне предупреждает, но присваивая uint16_tuint8_tпредупреждает. Это, конечно, не относится к указателям. Преобразование указателей в типы разного размера всегда требует явного приведения. Так что я думаю, что ответ на ваш вопрос просто "Нет".
Разумно предположить, что вопрос касается приведения указателей к этим типам, поскольку это суть предупреждения, которое он просит подавить. Но хорошо, что вы указали здесь на разные проблемы, поскольку комментарий ОП, похоже, не оценивает суть предупреждения. @user3386109 user3386109 - предупреждение предупреждает вас о приведении указателя - если вы, например, использовали его как массив или косвенно указали элемент, и у него был установлен бит знака, это может привести к непреднамеренному преобразованию без знака. Как я уже сказал в своем ответе, предупреждение касается /подписанности/, а не размера.
@BadZen Предупреждение о несовместимости типов. Система типов C несколько нефункциональна, поскольку она рассматривает char, signed char и unsigned char как 3 разных типа, независимо от того, какая подпись имеет char. Правила спецификаторов типов — одна из самых неправильных вещей в C... если бы в C были разумные правила спецификаторов типов, нам не разрешили бы писать #define very signed signed signed... very signed char ch; и тому подобную чепуху.
@Lundin - типы не являются «несовместимыми» в отношении неявного приведения типов. Вы можете неявно бросать между разными знаками. Предупреждение конкретно касается указателей на типы с другим знаком, как говорится прямо, и это /являются разными типами/, чем типы, на которые они указывают. В приведенном коде в этом месте фактически не выполняется неявное приведение типов между указанными.
Нет ничего под названием «неявное приведение» — есть неявные преобразования и явные преобразования, а приведение всегда является явным с использованием оператора приведения. Неявные преобразования разрешены только во время допустимых форм присваивания, имеющих дело с обычными переменными (арифметическими типами). Как я уже цитировал в другом комментарии, присваивание более придирчиво, когда речь идет о типах указателей — они должны быть совместимы и не могут удалять квалификаторы.





Скомпилируйте с помощью -Wall. Всегда компилируйте с -Wall.
<user>@squall:~/src/p1$ gcc -Wall -c test2.c
test2.c: In function ‘main’:
test2.c:9:31: warning: pointer targets in initialization of ‘const t_utf8 *’ {aka ‘const signed char *’} from ‘char *’ differ in signedness [-Wpointer-sign]
9 | t_utf8 const* utf8str = "你好世界";
| ^~~~~~~~~~~~~~
test2.c:11:13: warning: pointer targets in assignment from ‘const t_ascii *’ {aka ‘const char *’} to ‘const t_utf8 *’ {aka ‘const signed char *’} differ in signedness [-Wpointer-sign]
11 | utf8str = asciistr; // Ok: ascii to utf8 is safe
| ^
test2.c:12:14: warning: pointer targets in assignment from ‘const t_utf8 *’ {aka ‘const signed char *’} to ‘const t_ascii *’ {aka ‘const char *’} differ in signedness [-Wpointer-sign]
12 | asciistr = utf8str; // Should issue warning: utf8 to ascii is not safe
| ^
Вы хотите, чтобы кастинг из t_ascii из t_utf8 был безопасным, но это просто не так. Подпись отличается.
Предупреждение не о том, что валидный utf8 иногда не валидный ASCII — компилятор об этом ничего не знает. Предупреждение касается знака.
Если вам нужен беззнаковый char, скомпилируйте с -funsigned-char. Но тогда ни одно предупреждение не будет выдано.
(Кстати, если вы думаете, что тип int_least8_t сможет содержать многобайтовую кодировку char/полную кодовую точку utf8 — это не так. Все int_least8_t и, следовательно, utf8_t в одной единице компиляции будут иметь точно такой же размер.)
Независимо от того, является ли тип char знаковым или беззнаковым внутри, первый бит всегда должен быть равен 0 для допустимых символов ascii. Почему кастинг из t_ascii (он же char) в t_utf8 (он же int_least8_t) небезопасен? Как можно безопасно преобразовать t_ascii в t_utf8?
Соответствующий компилятор должен выдавать диагностическое сообщение при назначении указателя другому указателю, и они не являются совместимыми типами. Нет необходимости в -Wall как таковом, просто используйте соответствующий компилятор. gcc не соответствует настройкам по умолчанию, вот и все. clang в этом случае подходит даже с настройками по умолчанию.
@Lundin - соответствует /чему/? Пожалуйста, укажите стандарт, если вы делаете это утверждение. (В частности, это не стандарт ANSI C, в котором говорится: "Реализация может генерировать предупреждения во многих ситуациях, ни один из которых не определен как часть настоящего международного стандарта.. Ниже приведены некоторые из наиболее распространенных ситуаций...") Какие стандартные сведения о том, какие предупреждения компилятора должны быть выданы по умолчанию?
Соответствует стандарту C ISO IEC 9899:2018, 6.5.16.1 Простое назначение — ограничения. В частности, это должно быть истинным: «левый операнд имеет атомарный, квалифицированный или неквалифицированный тип указателя, и (учитывая тип, который будет иметь левый операнд после преобразования lvalue) оба операнда являются указателями на уточненные или неполные версии совместимых типов, и тип, на который указывает левый указатель, имеет все квалификаторы типа, на который указывает правый;".
Это ничего не говорит о том, какие предупреждения должен выдавать компилятор, и даже не относится ни к одному из операторов присваивания в этом посте. Я не думаю, что вы понимаете это, но это уже не по теме. Продолжим сторонний разговор.
Просто скомпилируйте его стандартным компилятором C. Какие параметры компилятора рекомендуются для начинающих изучать C?
Результат:
<source>: In function 'main':
<source>:9:31: error: pointer targets in initialization of 'const t_utf8 *' {aka 'const unsigned char *'} from 'char *' differ in signedness [-Wpointer-sign]
9 | t_utf8 const* utf8str = "你好世界"; // Ok
| ^~~~~~~~~~
<source>:11:14: error: pointer targets in assignment from 'const t_utf8 *' {aka 'const unsigned char *'} to 'const t_ascii *' {aka 'const char *'} differ in signedness [-Wpointer-sign]
11 | asciistr = utf8str; // Warning: utf8 to ascii is not safe
| ^
<source>:12:13: error: pointer targets in assignment from 'const t_ascii *' {aka 'const char *'} to 'const t_utf8 *' {aka 'const unsigned char *'} differ in signedness [-Wpointer-sign]
12 | utf8str = asciistr; // Ok: ascii to utf8 is safe
| ^
but not when implicitely casting t_ascii to t_utf8 ?
Нет, у вас не может быть этого в стандартном C, так как это недопустимое преобразование указателя. Вы можете отключить компилятор с помощью явного приведения типов, но если вы это сделаете, вы вызовете неопределенное поведение.
Кроме того, вы можете использовать C11 _Generic, чтобы узнать, какой тип uint_least8_t сводится к:
#include <stdint.h>
#include <stdio.h>
#define what_type(obj) printf("%s is same as %s\n", #obj, \
_Generic ((obj), \
char: "char", \
unsigned char: "unsigned char", \
signed char: "signed char") );
int main (void)
{
typedef char t_ascii;
typedef uint_least8_t t_utf8;
t_ascii ascii;
t_utf8 utf8;
what_type(ascii);
what_type(utf8);
}
Вывод на gcc x86 Linux:
ascii is same as char
utf8 is same as unsigned char
может
int_least8_tэто псевдонимchar?