Подтвердить что ты не робот

Почему это неявное преобразование (между разными типами указателей) допустимо?

Я оказался в следующей ситуации:

#include <stdio.h>

typedef struct T1 { int id; } T1;  
typedef struct T2 { int id; } T2;

void f(T1 *ptr) { printf("f called\n"); }

int main(void) 
{
    T2 obj; 
    T2 *ptr = &obj; 
    f(ptr); // shouldn't this be a compilation error ? 
    return 0;
}

конечно, это недопустимо С++, но в C программа печатает "f called". Как это справедливо?

ИЗМЕНИТЬ

(На всякий случай это неясно). Программа по-прежнему компилирует и запускается, если T2 "структурно" отличается, например

typedef struct T2 { double cc[23]; } T2;
4b9b3361

Ответ 1

Это неверно, если вы хотите принудительно выполнить стандартный совместимый код, важно скомпилировать с правильными флагами, например, как gcc, так и clang следующие флаги:

-std=c99 -pedantic-errors

создаст ошибку для диагностики, требуемую стандартом C99, и аналогичным образом вы можете использовать -std=c11 для C11. Это приведет к появлению следующей ошибки от gcc (увидеть ее в прямом эфире):

ошибка: передача аргумента 1 из 'f' из несовместимого типа указателя

У компиляторов есть расширения и разрешены такие функции, как implicit int из-за устаревшего кода, и важно знать разницу. Подробнее см. gcc document: Языковые стандарты, поддерживаемые GCC.

Быстрый способ увидеть, что это действительно недействительно, - перейти к Обоснование для языков международного стандартного программирования-C, в которых говорится в разделе 6.3.2.3 Указатели, которые имеют дело с преобразованиями, которые:

Неправильно преобразовать указатель на объект любого типа в указатель на объект другого типа без явного приведения.

Для получения более длинного пути мы перейдем к черновик проекта C99 6.5.2.2 Вызов функций, который говорит (акцент мой вперед):

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

и если мы перейдем к разделу 6.5.16 Операторы присваивания, который гласит:

Одно из следующих действий должно выполняться

и для указателей имеем:

  • оба операнда являются указателями на квалифицированные или неквалифицированные версии совместимых типов, а тип, на который указывает левый, имеет все квалификаторы типа, на которые указывает справа;
  • один операнд является указателем на объект или неполным типом, а другой - указателем на квалифицированную или неквалифицированную версию void и тип, на который указывают левые, имеет все квалификаторы типа указана справа;
  • левый операнд - это указатель, а справа - константа нулевого указателя;

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

Ответ 2

При компиляции я получаю следующее предупреждение:

temp.c: В функции main:

temp.c: 20: 5: warning: передать аргумент 1 из 'f из несовместимого типа указателя [включен по умолчанию]

temp.c: 13: 6: note: expected 'struct T1 *, но аргумент имеет тип' struct T2 *

Он "действителен", потому что они оба указатели и, следовательно, могут быть преобразованы, это просто не очень хорошая идея.

Ответ 3

В соответствии со стандартом C99, раздел 6.5.2.2 [вызовы функций], параграф 7, это valid разрешен в C, но недействителен.

Например, в вашем коде, если T1 и T2 представляют собой разные структуры, имеющие разные элементы, а адрес T2 передается в f() и принимается как T1*, то это абсолютно неправильно и результат является фатальным. На всякий случай, когда он скомпилирован [он должен был выпустить предупреждения о passing argument <number> of <a function> from incompatible pointer type], не означает, что это правильно.

В вашем коде, поскольку вы не получаете доступа к структурным переменным внутри f(), из-за совместимой оптимизации предупреждения могли быть исчезны.

Он читает

Если выражение, обозначающее вызываемую функцию, имеет тип, который включает прототип, аргументы неявно преобразуются, так как если по назначению, к типам соответствующих параметров, принимая тип каждого параметра является неквалифицированной версией его объявленный тип. Обозначение многоточия в прототипе функции declarator вызывает преобразование типа аргумента для остановки после последнего объявленный параметр. Активные объявления по умолчанию выполняются на конечные аргументы.

Ответ 4

Как отмечали другие, в C этот код требует диагностики (и, видимо, вы его получили, в виде предупреждения gcc).

Вы можете "исправить" код приложением:

f( (T1 *)ptr );

В вашей примерной программе это нормально. Однако в более сложной программе возникнет проблема. Поскольку T1 и T2 не являются совместимыми типами, то f записывает через указатель, а затем чтение через ptr (или наоборот) будет строгое сглаживание.

Вы можете обойти проблему, хотя, пользуясь тем, что союзы могут использоваться для сглаживания в C, и что существует специальное условие для членов объединения, являющихся структурами с общей начальной последовательностью:

union
{
    T1 t1;
    T2 t2;
} obj;

f( &obj.t1 );                 // might write to ptr->id
printf("%d\n", obj.t2.id);    // OK, writes int that f wrote

Так как пример объединения должен работать, любой здравомыслящий компилятор просто использовал бы общий макет для T1 и T2 и не выполнял бы никаких оптимизаций с строгим сглаживанием здесь, поэтому можно было бы разумно ожидать, что код с литой будет "просто работайте", как вы видите в своем примере.