Отсутствуют предупреждения GCC «-Wformat» для «%p» с символом * и т. д

Если я скомпилирую следующий код с -std=c17 -Wall -Wextra -Wpedantic с GCC 13.2.0, я не получу предупреждений, несмотря на то, что не использую void* в аргументах, соответствующих спецификаторам формата "%p".

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

int main()
{
    const char cstr[] = "ABC";
    size_t size = sizeof cstr;
    const uint8_t ustr[] = "ABC";
    const int8_t sstr[] = "ABC";
    const char* pcstr = cstr;
    const uint8_t* pustr = ustr;
    const int8_t* psstr = sstr;
    printf("cstr ptr:  %p\n", cstr);
    printf("size ptr:  %p\n", (void*)&size); // we need cast to prevent Wformat
    printf("&cstr ptr: %p\n", (void*)&cstr); // we also need this cast
    printf("pcstr:     %p\n", pcstr);
    printf("ustr ptr:  %p\n", ustr);
    printf("pustr:     %p\n", pustr);
    printf("sstr ptr:  %p\n", sstr);
    printf("psstr:     %p\n", psstr);
    return 0;
}

После прочтения вопросов и ответов *Что такое предупреждение и как его устранить: формат «%p» ожидает аргумент типа «void *», но аргумент 2 при распечатке имеет тип «int» [-Wformat=], следует ли мне ожидать здесь неопределенного поведения? Я попробовал несколько поисков, но вы поймете, насколько сложными они могут быть для такой специфической комбинации.

Может ли быть так, что void* и char-ish* имеют общие свойства, которые мне не хватает, или это просто случай отсутствующих предупреждений в GCC? Надеюсь, кто-то здесь сможет пролить свет на этот вопрос.

«Неопределенное поведение» означает, что стандарт не определяет никаких требований. Обычно компилятор не делает ничего конкретного для обеспечения согласованного поведения (поведения, определяемого реализацией). Таким образом, если типы указателей имеют одинаковый размер и порядок байтов, вы не ожидаете никакой разницы. Реальный вопрос заключается в том, почему он не выдает предупреждение - это определена реализация.

Clifford 18.04.2024 14:24

Это похоже на одно из предупреждений «технически UB, но на практике всегда работает». Кажется, Clang предупреждает об этом, а GCC — нет.

HolyBlackCat 18.04.2024 14:25

@Клиффорд Разве порядок байтов не имеет значения для void*?

Wolf 18.04.2024 14:36

@Wolf чисто теоретически, если разные типы указателей могут различаться по размеру, возможно, они также могут отличаться по порядку байтов. Дело в том, что %p правильно интерпретирует битовый шаблон указателя, он должен быть идентичен шаблону void*, который, как он предполагает, был передан. Возможно, не только порядок байтов; Сегмент 8086: адресация со смещением поднимает некоторые интересные проблемы, о которых нам, к счастью, по большей части не стоит беспокоиться.

Clifford 18.04.2024 14:44

Кроме того, стандарт C требует, чтобы каждый вид указателя на объект был конвертируем в void* и обратно. На практике это означает, что каждый указатель на объект должен использовать совместимый формат, и тот же формат будет использоваться и void*. Или как еще вы собираетесь удовлетворить это требование стандарта C каким-либо разумным способом?

Lundin 18.04.2024 16:15
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
5
122
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Вы не получаете предупреждение в случаях без приведения, поскольку void * должен иметь то же представление, что и указатель на тип символа, т. е. char, signed char и unsigned char, или любой тип, который является определением типа одного из этих типов. .

Это продиктовано разделом 6.2.5p28 стандарта C:

Указатель на void должен иметь такое же представление и выравнивание. требования как указатель на тип символа. 48)

  1. Те же требования к представлению и выравниванию предназначены для подразумевают взаимозаменяемость в качестве аргументов функций, возвращают значения из функции и члены профсоюзов.

Частично это связано с историей языка C до того, как void * существовал и char * использовался как универсальный тип указателя.

Однако в разделе 7.21.6.1p9, касающемся функции fprintf, говорится:

Если спецификация преобразования недействительна, поведение неопределенный. Если какой-либо аргумент не является правильным типом для соответствующую спецификацию преобразования, поведение не определено.

Таким образом, хотя на самом деле это выглядит как неопределенное поведение по букве стандарта, GCC (по характеру предупреждений, которые он выдает), похоже, позволяет использовать char * в местах, где void * ожидается как расширение из-за требования два типа имеют одинаковое представление.

Поэтому я бы пришел к выводу, что такие конструкции безопасны в GCC, хотя и не обязательно в других компиляторах. Например, вполне возможно, что некоторые компиляторы могут использовать соглашение о вызовах, при котором void * передается функции иначе, чем то, как она передает char *, но опять же, это кажется натяжкой, учитывая требования к представлению.

Фактически, раздел 7.16.1.1p2 относительно макроса va_arg гласит следующее:

... Если фактического следующего аргумента нет или тип несовместим с типом фактического следующего аргумента (как продвигается в соответствии с продвижения аргументов по умолчанию), поведение не определено, за исключением для следующих случаев:

  • один тип — это целочисленный тип со знаком, другой тип — соответствующий целочисленный тип без знака, а значение может быть представлено в виде оба типа;
  • один тип — это указатель на void, а другой — указатель на символьный тип.

Итак, предполагая, что fprintf/printf использует va_arg для чтения переменных аргументов, поведение будет определяться вышеизложенным, но, конечно, это делает предположение о поведении семейства функций printf.

По моему мнению, это недостаток стандарта, который следует устранить, чтобы такие преобразования были четко определены.

Именно на такой ответ я надеялся. Но сам я его найти не смог. Большое спасибо.

Wolf 18.04.2024 14:38

«Вполне возможно, что некоторые компиляторы могут использовать соглашение о вызовах, которое передает void * функции иначе, чем то, как она передает char *» — вы имеете в виду плавающие аргументы?

Wolf 18.04.2024 14:48

@Wolf Да, аргументы с плавающей запятой в некоторых случаях передаются в регистрах с плавающей запятой, а целочисленные значения передаются в стек. Так что теоретически разница возможна, но маловероятна.

dbush 18.04.2024 14:50

Здесь также актуально последнее предложение 7.6.11.1p2: «… если [аргумент типа va_arg] несовместим с фактическим типом следующего аргумента… поведение не определено… за исключением случая… . [один из двух] — указатель на void, а другой — указатель на тип символа». Это гарантирует, что void * и char * взаимозаменяемы при передаче вариативной функции, которая использует va_arg для доступа к вариативным аргументам. Однако я не могу найти каких-либо требований к функциям стандартной библиотеки с переменным числом вариантов (вести себя так, как если бы они) использовали va_arg для доступа к аргументам с переменным числом аргументов.

zwol 18.04.2024 17:02

(Я считаю, что обычное правило интерпретации «конкретных битов общего» будет означать, что последнее слово остается за 7.21.6.1p9, но я также считаю, что намерением комитета здесь было разрешить использование char * с %p без явного приведения.)

zwol 18.04.2024 17:04

Если аргумент передается в регистре (FP или GPR), он должен быть передан в кадр стека при выполнении &arg. Это также верно для переменной области функции, которая оптимизирована для размещения в регистре. Обратите внимание: void bar(int *y) { *y += 37; } ; int foo(int x) { bar(&x); x += 192; return x; }

Craig Estey 19.04.2024 05:31

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