Почему sizeof (x ++) не увеличивает x?

505

Вот код, скомпилированный в dev c ++ windows:

#include <stdio.h>

int main() {
    int x = 5;
    printf("%d and ", sizeof(x++)); // note 1
    printf("%d\n", x); // note 2
    return 0;
}

Я ожидаю, xчто будет 6 после выполнения примечания 1 . Тем не менее, вывод:

4 and 5

Может кто-нибудь объяснить, почему xне увеличивается после примечания 1 ?

Нейгил Р. Новаль
источник
37
Я хотел бы отметить, что DevC ++ использует очень старый устаревший компилятор, вы можете обновить его до более новой IDE, например, Codeblocks Eclipse или Visual Studio
Tom J Nowell,
4
++ x дает тот же результат, что и x ++, cygwin и gcc 3.4.4.
yehnan
6
@Tim Pletzcker Потому что первое значение - это размер переменной, а не сама переменная.
Surfbutler
Мой ответ был добавлен из-за слияния с этим. Мой ответ на самом деле показывает, как вы можете получить оценку во время выполнения, в случае, если VLAsни один из остальных не делает.
Шафик Ягмур
2
такого рода письма следует избегать, так как это может вызвать нежелательные побочные эффекты. Ваш вопрос уже один из них.
user666412 22.09.16

Ответы:

537

Из стандарта C99 (акцент мой)

6.5.3.4/2

Оператор sizeof возвращает размер (в байтах) своего операнда, который может быть выражением или именем типа в скобках. Размер определяется по типу операнда. Результатом является целое число. Если тип операнда является типом массива переменной длины, операнд оценивается; в противном случае, операнд не оценивается, а результатом является целочисленная константа.

PMG
источник
51
"Если тип операнда является типом массива переменной длины, операнд оценивается" вау! Я никогда не осознавал этого
Кос
6
что вы подразумеваете под типом массива переменной длины? Это значит, что операнд является массивом? Код в этом случае не является массивом. Вы можете прояснить ситуацию для меня?
Нейгил Р. Новал
37
Массив переменной длины - это массив, объявленный с размером, неизвестным во время компиляции, например, если вы читаете Nиз stdin и делаете make int array[N]. Это одна из функций C99, недоступная в C ++.
Кос
21
@LegendofCage, в частности это будет означать, что в чем-то вроде sizeof(int[++x])(действительно, действительно плохая идея, во всяком случае) ++можно было бы оценить.
Йенс Густед
3
@Joe Wreschnig: Она оценивается gcc, clangи в ideone.com/Pf7iF
JFS
190

sizeofявляется оператором времени компиляции, поэтому во время компиляции sizeofего операнд заменяется значением результата. Операнд не вычисляется (кроме случаев , когда это переменная массива длины) на всех; имеет значение только тип результата.

short func(short x) {  // this function never gets called !!
   printf("%d", x);    // this print never happens
   return x;
}

int main() {
   printf("%d", sizeof(func(3))); // all that matters to sizeof is the 
                                  // return type of the function.
   return 0;
}

Вывод:

2

как shortзанимает 2 байта на моей машине.

Изменение типа возвращаемого значения функции double:

double func(short x) {
// rest all same

даст в 8качестве вывода.

codaddict
источник
12
Только иногда - это время компиляции, если это возможно.
Мартин Беккет
10
-1, так как это противоречит принятому (и правильному) ответу и не цитирует стандарт.
Сам Хочевар
1
Есть хорошее преимущество для разрешения времени компиляции оператора sizeof () при работе со строками. Если у вас есть строка, которая инициализируется как строка в кавычках, вместо использования strlen (), где массив символов, содержащий строку, должен быть проверен на наличие нулевого терминатора во время выполнения, sizeof (quoted_string) известен во время компиляции, и, следовательно, во время выполнения. Это мелочь, но если вы используете строку в кавычках в цикле миллионы и миллионы раз, это существенно влияет на производительность.
user2548100
Если вы действительно используете его миллионы и миллионы раз в цикле, разве не будет гораздо разумнее вычленить вычисление длины из цикла? Я, конечно, надеюсь, что в вашем коде нет миллионов и миллионов различных жестко закодированных констант. : -o
Веки
47

sizeof(foo) очень старается определить размер выражения во время компиляции:

6.5.3.4:

Оператор sizeof возвращает размер (в байтах) своего операнда, который может быть выражением или именем типа в скобках. Размер определяется по типу операнда. Результатом является целое число. Если тип операнда является типом массива переменной длины, операнд оценивается; в противном случае, операнд не оценивается, а результатом является целочисленная константа.

Вкратце: массивы переменной длины, запускаемые во время выполнения. (Примечание. Массивы переменной длины - это особая функция, а не массивы, выделенные с помощью malloc(3).) В противном случае вычисляется только тип выражения и тот, который используется во время компиляции.

sarnold
источник
33

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

(sizeof x)  //this also works
hugomg
источник
1
Но как это ответ на вопрос?
Себастьян Мах
5
@phresnel: Это просто, чтобы прояснить, что sizeof «странный» и не подчиняется правилам обычных функций. Я все равно отредактировал пост, чтобы устранить возможную путаницу с обычными операторами времени выполнения, такими как (+) и (-)
hugomg
sizeofОператор не оператор времени компиляции, вам нужно только дать ему VLA , чтобы понять это.
paxdiablo
21

Запись

Этот ответ был объединен с дубликатом, который объясняет позднюю дату.

оригинал

За исключением массивов переменной длины sizeof не оценивает его аргументы. Мы можем видеть это из стандартного раздела проекта C99 6.5.3.4 Оператор SizeOf пункта 2 , который гласит:

Оператор sizeof возвращает размер (в байтах) своего операнда, который может быть выражением или именем типа в скобках. Размер определяется по типу операнда. Результатом является целое число. Если тип операнда является типом массива переменной длины, операнд оценивается; в противном случае, операнд не оценивается, а результатом является целочисленная константа.

Комментарий ( теперь удаленный ) спросил, будет ли что-то подобное оцениваться во время выполнения:

sizeof( char[x++]  ) ;

и действительно, что-то подобное будет работать ( смотрите их вживую ):

sizeof( char[func()]  ) ;

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

Обратите внимание, что массивы переменной длины описаны в черновом варианте стандартного раздела C99, описанном в разделе 6.7.5.2 4 :

[...] Если размер является целочисленным константным выражением, а тип элемента имеет известный постоянный размер, тип массива не является типом массива переменной длины; в противном случае тип массива является типом массива переменной длины.

Обновить

В C11 ответ изменяется для случая VLA, в некоторых случаях не определено, оценивается ли выражение размера или нет. Из раздела 6.7.6.2 Array объявлений, в котором говорится:

[...] В тех случаях, когда выражение размера является частью операнда оператора sizeof, и изменение значения выражения размера не повлияет на результат оператора, неизвестно, будет ли вычисляться выражение размера.

Например, в случае, как это ( см. Это в прямом эфире ):

sizeof( int (*)[x++] )
Шафик Ягмур
источник
1
Здесь важно помнить, что большую часть времени это, sizeofпо сути, макрос - он не создает код, а предварительно вычисляет ожидаемое значение и вносит его прямо в код. Обратите внимание, что это было единственное поведение до C99, поскольку VBA не существовало (я никогда не слышал о них до этого ответа, верьте этому или нет!)
Корли Бригман
Каким образом будет sizeof (char[x++]);использоваться значение xдля чего-либо, кроме определения значения выражения x++и нового значения для x, оба из которых являются нормальными для этого оператора?
суперкат
@alk ... эээ, да, я имел в виду 'VLA', конечно :) Shafik - почему они оценивают во время выполнения? Как я уже сказал, я никогда не видел VLA, но типы этих файлов известны во время компиляции, не так ли?
Корли Бригман
@CorleyBrigman snappy ответом будет b / c, так говорит стандарт, но причина в том, что b / c мы не знаем размер массива во время компиляции, поэтому мы должны оценить выражение во время выполнения. VLA - интересная тема, вот два поста, которые у меня есть, здесь и здесь .
Шафик Ягмур
@Shafik - ааа, хорошо, я думаю, что мое замешательство было то, что я не осознавал, char[x++]что это VLA это выглядит эффективно, как char*мои незнакомые глаза.
Корли Бригман
11

Поскольку операнд sizeofоператора не оценивается, вы можете сделать это:

int f(); //no definition, which means we cannot call it

int main(void) {
        printf("%d", sizeof(f()) );  //no linker error
        return 0;
}

Демо онлайн: http://ideone.com/S8e2Y

То есть вам не нужно определять функцию, fесли она используется sizeofтолько в. Эта техника в основном используется в метапрограммировании шаблонов C ++, поскольку даже в C ++ операнд sizeofне оценивается.

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

В C ++ вы можете даже это:

struct A
{
  A(); //no definition, which means we cannot create instance!
  int f(); //no definition, which means we cannot call it
};

int main() {
        std::cout << sizeof(A().f())<< std::endl;
        return 0;
}

Тем не менее, похоже, sizeofчто сначала я создаю экземпляр A, пишу A(), а затем вызываю функцию fэкземпляра, пишу A().f(), но ничего подобного не происходит.

Демо: http://ideone.com/egPMi

Вот еще одна тема, которая объясняет некоторые другие интересные свойства sizeof:

Наваз
источник
10

Выполнение не может произойти во время компиляции. Так ++i/ i++не случится. Также sizeof(foo())не будет выполнять функцию, но вернет правильный тип.

Ракеш
источник
2
« Выполнение не может произойти во время компиляции». Что вы имеете в виду?
любопытный парень
1
Компиляция создаст только объектный код ... Объектный код будет выполняться только тогда, когда пользователь выполняет двоичный файл. Поскольку sizeof происходит во время компиляции, предполагая, что i ++ будет увеличиваться, что неправильно.
Ракеш
« Как sizeof происходит во время компиляции », вы имеете в виду: «как sizeofвыражение постоянной времени компиляции»?
любопытный парень
Как «#define» происходит во время предварительной обработки, так же sizeof будет происходить во время компиляции. Во время компиляции доступна вся информация о типе, поэтому sizeof тут же оценивается во время компиляции, и значение заменяется. Как уже упоминалось @pmg "Из стандарта C99" раньше.
Ракеш
1
« sizeof будет происходить во время компиляции » для чего-то, что не является массивом переменной длины
curiousguy
0

sizeofвыполняется во время компиляции, но x++может оцениваться только во время выполнения. Чтобы решить эту проблему, стандарт C ++ диктует, что операнд sizeofне оценивается. Стандарт C говорит:

Если тип операнда [of sizeof] является типом массива переменной длины, операнд вычисляется; в противном случае, операнд не оценивается, а результатом является целочисленная константа.

Эдвард Карак
источник
В C ++ нет VLA.
LF
-2

sizeof() Оператор задает только размер типа данных, он не оценивает внутренние элементы.

Манна
источник
Это неверно, sizeof()оператор действует рекурсивно и получает размер в байтах всех элементов контейнера, членов класса или структуры и т. Д. Вы можете легко доказать это сами, создав простой класс с несколькими членами и зову sizeof()на это. (Тем не менее, все, что там является указателем, не может видеть размер - только размер указателя.) Это происходит все во время компиляции, как заявили другие комментаторы: выражения внутри sizeof()не оцениваются.
Тайлер Шеллберг