printf
немного забавно, потому что это одна из тех функций, которая принимает varargs . Итак, давайте разберемся с этим, написав вспомогательную функцию bar
. Мы вернемся к printf
позже.
(я использую "gcc (Ubuntu 4.4.3-4ubuntu5) 4.4.3")
void bar(const char *t) {
printf("bar: %s\n", t);
}
и вызов этого вместо:
bar(foo().f); // error: invalid use of non-lvalue array
ОК, выдает ошибку. В C и C ++ вам не разрешено передавать массив со значением . Вы можете обойти это ограничение, поместив массив в структуру, например void bar2(Foo f) {...}
Но мы не используем этот обходной путь - нам не разрешено передавать массив по значению. Теперь вы можете подумать, что он должен уменьшиться до char*
, что позволит вам передать массив по ссылке. Но затухание работает только в том случае, если у массива есть адрес (т.е. это l-значение). Но временных , таких как возвращаемые значения из функции, живут в волшебной стране, где у них нет адреса. Поэтому вы не можете взять адрес &
временного. Короче говоря, нам не разрешено брать временный адрес, и, следовательно, он не может распадаться на указатель. Мы не можем передать его ни по значению (потому что это массив), ни по ссылке (потому что это временно).
Я обнаружил, что работает следующий код:
bar(&(foo().f[0]));
но, честно говоря, я думаю, что это подозрительно. Разве это не нарушает правила, которые я только что перечислил?
И чтобы быть полным, это работает отлично, как и должно:
Foo f = foo();
bar(f.f);
Переменная f
не является временной, и поэтому мы можем (неявно, во время затухания) взять ее адрес.
printf, 32-битный против 64-битный и странный
Я обещал еще раз упомянуть printf
. В соответствии с вышесказанным, он должен отказаться передавать foo () .f любой функции (включая printf). Но printf забавен, потому что это одна из тех функций vararg. gcc позволил себе передать массив по значению в printf.
Когда я впервые скомпилировал и запустил код, он был в 64-битном режиме. Я не видел подтверждения своей теории, пока не скомпилировал 32-битную версию (-m32
в gcc). Конечно же, я получил segfault, как в первоначальном вопросе. (Я получал несколько бессмысленных выходных данных, но без ошибки, когда в 64 битах).
Я реализовал свой собственный my_printf
(с абсурдом vararg), который печатал действительное значение char *
, прежде чем пытаться печатать буквы, на которые указывает char*
. Я назвал это так:
my_printf("%s\n", f.f);
my_printf("%s\n", foo().f);
и это вывод, который я получил ( код на ideone ):
arg = 0xffc14eb3 // my_printf("%s\n", f.f); // worked fine
string = Hello, World!
arg = 0x6c6c6548 // my_printf("%s\n", foo().f); // it's about to crash!
Segmentation fault
Первое значение указателя 0xffc14eb3
является правильным (оно указывает на символы «Привет, мир!»), Но посмотрите на второе 0x6c6c6548
. Это коды ASCII для Hell
(обратный порядок - немного порядка байтов или что-то в этом роде). Он скопировал массив по значению в printf, и первые четыре байта были интерпретированы как 32-битный указатель или целое число. Этот указатель нигде не указывает, и, следовательно, программа падает, когда пытается получить доступ к этому местоположению.
Я думаю, что это является нарушением стандарта, просто в силу того факта, что нам не разрешено копировать массивы по значению.