Оператор 'sizeof' с условным (тройным) выражением
Мне трудно понять поведение sizeof
при задании тройного выражения.
#define STRING "a string"
int main(int argc, char** argv)
{
int a = sizeof(argc > 1 ? STRING : "");
int b = sizeof(STRING);
int c = sizeof("");
printf("%d\n" "%d\n" "%d\n", a, b, c);
return 0;
}
В этом примере (проверенном с gcc 4.4.3 и 4.7.2, скомпилированным с -std=c99
), b равно 9 (8 символов + неявный '\0'
), c - 1 (неявный '\0'
). a по какой-то причине 4.
Я ожидал бы, что a будет либо 9, либо 1, в зависимости от того, является ли argc больше 1. Я думал, что, возможно, строковые литералы преобразуются в указатели перед передачей на sizeof
, вызывая sizeof(char*)
равным 4.
Я попытался заменить STRING
и ""
на char массивы...
char x[] = "";
char y[] = "a string";
int a = sizeof(argc > 1 ? x : y);
... но я получил те же результаты (a = 4, b = 9, c = 1).
Затем я попытался погрузиться в спецификацию C99, но я не нашел в ней каких-либо очевидных объяснений. Из любопытства я также попытался изменить изменения x и y на другие типы:
-
char
и long long int
: a становится 8
- оба
short
или оба char
: a становится 4
Итак, определенно происходит какое-то преобразование, но я изо всех сил пытаюсь найти какое-либо официальное объяснение. Я могу себе представить, что это произойдет с арифметическими типами (я смутно знаю, что много продвижений происходит, когда они задействованы), но я не понимаю, почему строковый литерал, возвращаемый тернарным выражением, будет преобразуется в размер 4.
NB: на этом аппарате sizeof(int) == sizeof(foo*) == 4
.
Последующий
Спасибо за ребята. Понимание того, как работают sizeof
и ?:
, побудило меня попробовать еще несколько типов mashup и посмотреть, как реагирует компилятор. Я редактирую их для полноты:
foo* x = NULL; /* or foo x[] = {} */
int y = 0; /* or any integer type */
int a = sizeof(argc > 1 ? x : y);
Допустимые значения warning: pointer/integer type mismatch in conditional expression [enabled by default]
и a == sizeof(foo*)
.
С foo x[], bar y[]
, foo* x, bar* y
или foo* x, bar y[]
предупреждение становится pointer type mismatch
. Предупреждение при использовании void*
.
float x = 0; /* or any floating-point type */
int y = 0; /* or any integer type */
int a = sizeof(argc > 1 ? x : y);
Не дает никаких предупреждений и a == sizeof(x)
(т.е. тип с плавающей запятой).
float x = 0; /* or any floating-point type */
foo* y = NULL; /* or foo y[] = {} */
int a = sizeof(argc > 1 ? x : y);
Допустимые значения error: type mismatch in conditional expression
.
Если я когда-либо прочитал спецификацию, я обязательно отредактирую этот вопрос, чтобы указать на соответствующие части.
Ответы
Ответ 1
Вам нужно понять выражения, которые являются основным компонентом языка.
Каждое выражение имеет тип. Для выражения e
, sizeof e
- размер типа значения выражения e
.
Выражение a ? b : c
имеет тип. Тип - это общий тип двух выражений операнда b
и c
.
В вашем примере общий тип char[9]
и char[1]
равен char *
(оба выражения с матрицей распадаются на указатель на первый элемент). (В С++ правила для строковых литералов различны и везде const
.)
Ответ 2
Вам нужно понять, что sizeof
является полностью компилятором.
С VLA он может возвращать переменное выражение, в противном случае это константа времени компиляции.
Важным является тип его аргумента.
Итак, в sizeof(argc > 1 ? STRING : "")
условие не оценивается. Тип аргумента разлагается до const char*
. И на вашей машине это 4.
Вместо этого вы должны указать (argc > 1)?sizeof(STRING):1
Так как STRING
является макрорасширенным до литерала "a string"
, sizeof(STRING)
равно 9, как если бы вы объявили
const char STRING[] = {'a',' ','s','t','r','i','n','g','\0'};
Ответ 3
Оба STRING
и ""
являются объектами массива типов char[9]
и char[1]
соответственно. В языке C, когда объекты массива используются в выражениях, они получают неявное преобразование (распад) в типы указателей почти во всех контекстах с несколькими известными исключениями.
Одним из таких исключений является оператор sizeof
. Когда вы используете объект массива как непосредственный операнд sizeof
, этот объект массива не распадается на тип указателя, и вы получите размер всего массива в байтах в качестве результата. Вот почему sizeof(STRING)
эквивалентно sizeof(char[9])
и оценивается как 9
. И sizeof("")
эквивалентен sizeof(char[1])
и оценивается как 1
.
Но когда вы используете объекты массива в качестве операндов оператора ?:
, контекст больше не является исключительным. В контексте ?:
массивы операторов сразу же распадаются на указатели. Это означает, что ваш sizeof(argc > 1 ? STRING : "")
эквивалентен sizeof(argc > 1 ? (char *) STRING : (char *) "")
и, в свою очередь, эквивалентен sizeof(char *)
. Это оценивает размер указателя на вашей платформе, который как раз бывает 4
.