C жалуется на передачу значения char ** в функцию, принимая char const * const * const, но C ++ не делает

Мне трудно понять, почему C ++ ведет себя более «расслабленно», чем C, когда дело доходит до интерпретации и создания типов для параметров функции.

C делает самую простую вещь в мире, она привязана к тому, что вы пишете, и все, C ++, с другой стороны, работает извращенно, что я не могу понять.

например популярный argv который является char* [] когда передается функция становится char** и я действительно не понимаю, почему, что я ожидаю и «хочу» char * const * но я получил это поведение вместо

Вы также можете прочитать эта статья в PDF что говорит об этих различиях между C и C ++, статья также заканчивается этой фразой:

Хотя C ++ игнорирует cv-квалификаторы верхнего уровня в параметре
декларации при определении сигнатур функций, это не
полностью игнорировать эти cv-квалификаторы.

и поскольку я не могу найти эту проблему в Интернете (программирование встроенных систем — февраль 2000 г., а старые проблемы бесплатны), мне интересно, что эта фраза может означать.

Кто-то может объяснить, почему это поведение такое, как в C ++?

РЕДАКТИРОВАТЬ:

Один из моих примеров

#include <stdio.h>

void foo(int argc, const char *const *const argv) {
printf("%d %s\n", argc, argv[0]);
}

int main(int argc, char *argv[]) {
foo(argc, argv);
return (0);
}

и если вы скомпилируете это с gcc 4.8.1 вы получите ожидаемую ошибку

gcc cv_1.c
cv_1.c: In function ‘main’:
cv_1.c:8:3: warning: passing argument 2 of ‘foo’ from incompatible pointer type [enabled by default]
foo(argc, argv);
^
cv_1.c:3:6: note: expected ‘const char * const* const’ but argument is of type ‘char **’
void foo(int argc, const char *const *const argv) {
^

этот вывод подразумевает тот факт, что argv интерпретируется как char**

3

Решение

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

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

void f(int       i);
void f(int const i);

Теперь вопрос, учитывая вызов f(1) какая из двух перегрузок должна быть выбрана? Проблема здесь в том, что, является ли аргумент постоянным или нет, не влияет на то, из чего он может быть построен, поэтому компилятор никогда не сможет решить, что является корректной перегрузкой. Решение простое: в сигнатуре отбрасывается квалификатор верхнего уровня, и обе они являются одной и той же функцией.

6

Другие решения

Ссылочная статья в PDF содержит ряд неверных утверждений о различиях между C и C ++ в их обработке cv-квалификаторов верхнего уровня. Эти различия либо не существуют, либо имеют характер, отличный от того, что подразумевается в статье.

В действительности и C и C ++ эффективно игнорировать cv-квалификаторы верхнего уровня в объявлениях параметров функции, когда дело доходит до определения сигнатуры функции и типа функции. Формулировки в стандартах языка C и C ++ (и лежащих в их основе механизмах) могут концептуально отличаться, но конечный результат одинаков для обоих языков.

C ++ действительно напрямую игнорирует cv-квалификаторы верхнего уровня для параметров при определении типа функции, как описано в 8.3.5 / 5: «После создания списка типов параметров любые cv-квалификаторы верхнего уровня, модифицирующие тип параметра, удаленный при формировании типа функции. «

Вместо того, чтобы напрямую игнорировать такие классификаторы, C опирается на специфическое для C понятие совместимый тип. Это говорит о том, что типы функций, которые отличаются только по cv-квалификаторам верхнего уровня по параметрам совместимый, что для всех средств и целей означает, что они одинаковы. Определение совместимости типов функций в 6.7.5.3/15 гласит: «При определении совместимости типов и составного типа […] каждый параметр, объявленный с квалифицированным типом, принимается как имеющий неквалифицированный версия его заявленного типа. «

В связанной статье PDF говорится, что в C следующая последовательность объявлений является незаконной

void foo(int i);
void foo(const int i);

На самом деле это совершенно законно в C. С просто требует, чтобы все объявления одной и той же сущности в одной и той же области использовались совместимый типы (6,7 / 4). Две декларации выше совместимый, это означает, что они просто юридически переопределяют ту же функцию. (В C ++ вышеприведенные объявления также являются законными, и они также объявляют ту же функцию.)

Для дополнительных примеров в C и C ++ допустимы следующие инициализации

void foo(const int i);
void bar(int i);

void (*pfoo)(int) = foo;       // OK
void (*pbar)(const int) = bar; // OK

В то же время и C и C ++ одинаково принять во внимание cv-квалификаторы верхнего уровня, когда дело доходит до определения типа локальной переменной параметра функции. Например, в C и C ++ следующий код некорректен

void foo(const int i) {
i = 5; // ERROR!
}

И в C, и в C ++ функция, объявленная с одной cv-квалификацией верхнего уровня по своим параметрам, может быть позже определена с совершенно другой cv-квалификацией ее параметров. Любые различия в cv-квалификации верхнего уровня не являются перегрузкой функций в C ++.


Кроме того, вы неоднократно упоминали, что char *[] интерпретируется как char ** как-то актуально. Я не вижу актуальности. В списках параметров функции T [] декларации всегда эквивалентны T * деклараций. Но это не имеет абсолютно никакого отношения к cv-квалификаторам верхнего уровня.

Между тем, пример кода в вашем редакторе не может быть скомпилирован по причине, которая также не имеет ничего общего с cv-квалификаторами верхнего уровня. Он не компилируется, потому что нет неявного преобразования из char ** в const char *const * на языке Си. Обратите внимание, что это преобразование не включает и не заботится о cv-квалификаторах верхнего уровня вообще. const квалификаторы, которые влияют на это преобразование, присутствуют только на первом и втором уровнях косвенности.

Это действительно связано с различием между C и C ++. В C и C ++ неявное преобразование из char ** в const char ** не допускается (см. Вот например). Тем не менее, C ++ позволяет неявное преобразование из char ** в const char *const * в то время как C до сих пор нет. Вы можете прочитать больше об этом Вот. Но обратите внимание, опять же, что во всех этих случаях cv-квалификаторы верхнего уровня совершенно не имеют значения. Они не играют никакой роли вообще.

5

Это что-то вроде предположения, но причина в том, что параметр функции с квалификаторами является только копией аргумента. Рассматривать:

void foo(int * const a, volatile int b) { … }

Эти квалификаторы говорят, что код в определении функции не будет изменяться a (потому что это const) и что значение b могут быть доступны способами, неизвестными реализации C ++. (Это довольно странно; изменчивыми объектами обычно являются такие вещи, как аппаратные регистры или, возможно, данные, разделяемые между процессами. Но допустим, мы отлаживаем проблему, поэтому мы временно отметили b volatile для обеспечения доступа к нему в отладчике.)

Реализация C ++ должна соблюдать эти классификаторы на a а также b когда он компилирует и выполняет код, который определяет fooпоэтому он не может игнорировать эти классификаторы.

Тем не менее, рассмотреть мнение вызывающего foo, Дело в том, что foo лечит a как постоянный или b как изменчивый не имеет значения для вызывающего абонента. Какие бы аргументы он ни указывал, они были скопированы (например, в регистры или в стек) для передачи foo, Все, что он сделал, это передал ценности. Если декларация foo не было классификаторов:

void foo(int *a, int b) { … }

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

4

void foo( char const * const * const) {}
void bar( char *x[]) {
foo(x); // warning in C, nothing in C++
}

Причина, по которой компилируется этот пример как C, выдает предупреждение, но C ++ не производит никакой диагностики, не потому, что C и C ++ обрабатывают char *[] как разные типы, или потому что они отбрасывают или вставляют const в разных местах, но просто потому, что C и C ++ по-разному определяют «совместимые типы указателей»; C ++ ослабляет правила, потому что строгие правила C не предотвращают реальных ошибок.

Подумайте: что именно вы можете сделать с char const * const * const это не законно делать с char **? Поскольку никакие модификации не могут быть сделаны, невозможно внести какие-либо ошибки, и поэтому такое ограничение не имеет большого значения.

Однако это не означает, что вставка consts не разрешает код, который может вызвать ошибку. Например:

void foo(char const **c) { *c = "hello"; }

void bar(char **c) {
foo(c);
**c = 'J';
}

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

C ++ тщательно определяет несовместимые типы указателей, так что вышеупомянутое не допускается, в то же время ослабляя правила из C, чтобы разрешить более безопасные программы, чем C.

Одним из преимуществ правил C является то, что они очень просты. В принципе:

Для совместимости двух типов указателей оба должны быть одинаково квалифицированы, и оба должны
быть указателями на совместимые типы.

а также

Для любого квалификатора q указатель на неквалифицированный тип может быть преобразован в указатель на
q-квалифицированная версия типа; значения хранятся в исходных и преобразованных указателях
сравните равных.

С другой стороны, правила C ++ действуют для нескольких параграфов и используют сложные определения, чтобы точно указать, какие преобразования указателей разрешены. Кроличья нора начинается с C ++ 11 4.4 [conv.qual] параграф 4.


Мне интересно, что эта фраза может означать.

Он, скорее всего, ссылается на тот факт, что если параметр определяется как const при определении функции, то компилятор не позволит определению функции выполнять неконстантные операции с параметром.

void foo(int x);
void bar(int x);

void foo(int const x) {
++x; // error, parameter is const
}

void bar(int x) {
++x; // okay, parameter is modifiable.
}
3

Наблюдения слишком велики для комментариев.

Только первый const в char const * const * const x вызывает предупреждение C
C ++ (Visual) жалуется на 2 из 8. Не знаете почему?

ИМХО: ни один язык не дифференцируется на 3-й const с точки зрения вызывающих функций излишне.

void fooccc( char const * const * const x) { if(x) return; }
void foocc_( char const * const *       x) { if(x) return; }
void fooc_c( char const *       * const x) { if(x) return; }
void fooc__( char const *       *       x) { if(x) return; }
void foo_cc( char       * const * const x) { if(x) return; }
void foo_c_( char       * const *       x) { if(x) return; }
void foo__c( char       *       * const x) { if(x) return; }
void foo___( char       *       *       x) { if(x) return; }

int g(char *x[]) {
fooccc(x); // warning in C passing argument 1 of 'fooccc' from incompatible pointer type
foocc_(x); // warning in C "fooc_c(x); // warning in C "   error in C++ cannot convert parameter 1 from 'char *[]' to 'const char **const ' Conversion loses qualifiers
fooc__(x); // warning in C "   error in C++ cannot convert parameter 1 from 'char *[]' to 'const char **'       Conversion loses qualifiers
foo_cc(x); // no problem in C  no problem in C++
foo_c_(x); // no problem in C  no problem in C++
foo__c(x); // no problem in C  no problem in C++
foo___(x); // no problem in C  no problem in C++
return 0;
}

Примечания: Eclipse, gcc -std = c99 -O0 -g3 -Wall
C ++ Visual Studio 10.0

1
По вопросам рекламы [email protected]