2013-12-03 9 views
3

У меня возникли трудности с пониманием того, почему C++ ведет себя более «расслабленным», чем C, когда дело доходит до интерпретации и создавая типы для параметров функции.C жалуется на передачу char ** значение на функцию char const * const * const, но C++ не

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

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

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

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

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

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

EDIT:

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

#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**

+7

Ваш пример ARGV на самом деле то, что C++ унаследовал от C. Ваших PDF переговоров о * подписях *, они имеют значение только для разрешения перегрузки. Не имеет ничего общего с проверкой cv. –

+0

@HansPassant для меня, этот механизм по-прежнему оставляет мой 'const', если он присутствует на верхнем уровне, и я хотел бы знать, в чем причина этого странного поведения. 'argv' - это просто игрушка в этом примере. – user2485710

+3

Юмор нам с конкретным примером, пожалуйста. –

ответ

6

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

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

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

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

+0

Я не понимаю, и ключевой шаг для меня - это именованная переменная, возможно, имеет cv-квалификатор, из того, что вы говорите, похоже, что это что-то сделано, чтобы сохранить временные значения от создания проблем, выглядит как компромисс здесь; но можете ли вы обратиться к определенному абзацу из стандарта C++? – user2485710

+0

@ user2485710: Нет, проблема связана не с временными, а с разрешением перегрузки. Если вы хотите, замените '1' на именованную переменную выше. Квалификатор верхнего уровня применяется к аргументу функции. Объекты, которые либо const, либо не const, создаются точно так же, тот же набор аргументов для конструктора (или функции преобразования). Верхний уровень 'const' не имеет отношения к вызывающему и не может быть использован компилятором для различения одного вызова другому. Выберите функцию, добавьте/удалите квалификаторы верхнего уровня, рассмотрите, какие вызовы действительны с любой подписью: то же самое. –

4

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

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

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

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

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

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

то поведение звонящего не изменится: В любом случае, он просто передает значения аргументов и вызывает foo. Следовательно, эти два объявления foo идентичны по виду вызывающего, поэтому они имеют одну и ту же подпись.

+0

Итак, в двух словах вы говорите, что никаких изменений в наблюдаемом поведении (для того, что из «foo») нет, и этого достаточно, чтобы пересмотреть масштабы этой проблемы и сказать, что, может быть, это так хорошо, как есть? – user2485710

3
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 **? Поскольку никакие изменения не могут быть сделаны, невозможно ввести какие-либо ошибки, и поэтому такое ограничение не имеет большого значения.

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

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

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

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

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

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

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

и

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

С другой стороны, правила C++ распространяются на несколько абзацев и используют сложные определения, чтобы точно указать, какие преобразования указателей допускаются. Отверстие кролика начинается 11 4.4 C++ [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. 
} 
+0

Мне все еще не ясно, почему 'argv' интерпретируется как' char ** 'компилятором (см. Мое редактирование для первого сообщения с примером), так как это' char * [] ', что в значительной степени является точкой где я начал экспериментировать с этим. – user2485710

+0

@ user2485710 Какая разница между параметрами 'char * []' и 'char **' в C? – bames53

+0

где 'a' is' int a [] ', я вижу' a' как 'int * const a', поэтому' char ** 'для' argv' это действительно не то, что я ожидаю увидеть. – user2485710

5

формата 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. 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-квалификации по своим параметрам могут быть впоследствии определен с совершенно другой сорта -квалификация его параметров. Любые различия в квалификации высшего уровня не представляют собой перегрузку функций на C++.


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

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

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

+0

Каков абзац в стандарте, который описывает это? это же верно для шаблонов? – user2485710

+0

Держу пари. Что касается статьи, она довольно старая. За несколько лет до 2000 года люди вокруг меня до сих пор с удовольствием использовали компилятор IIRC «Turbo C++ 32», который работал в среде DOS и имел множество проблем, когда дело касалось всех важных бит. Возможно, автор статьи просто использовал что-то вроде этого. – quetzalcoatl

1

Замечания слишком большие для комментариев.

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

ИМХО: ни один из языков не отличается от третьего 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

+0

Я считаю, что правила C++ сводятся к: вставка const на любом уровне требует, чтобы константа была вставлена ​​на все более высокие уровни, за исключением верхнего уровня, который не имеет значения. – bames53

+0

@ bames53 Если бы у вас был готовый доступ к другому компилятору C++, сообщите о своих выводах и информации о компиляторе. – chux

+0

Есть несколько онлайн-компиляторов, перечисленных на сайте [isocpp.org] (http://isocpp.org/get-started), однако вместо того, чтобы интенсивно тестировать реализации, я просто читаю спецификацию C++. ваши результаты здесь совпадают с моим чтением и с несколькими быстрыми тестами (clang и vC++) – bames53

Смежные вопросы