C++. Ошибка с инкрементом (непойму почему :-\)

Модератор: Модераторы разделов

Аватара пользователя
innkeeper
Сообщения: 110

C++. Ошибка с инкрементом

Сообщение innkeeper »

Код: Выделить всё

/* why.cpp */
#include <iostream>

int main() {
    int i=0;

    std::cout << i++ << "\t" << i++ << "\n";

    return 0;
}


Код: Выделить всё

22:28 kip@kepka:~/programming/c$ g++ why.cpp -o why
22:28 kip@kepka:~/programming/c$ ./why
1       0


gcc version 3.3.6
glibc version 2.3.5
Slackware

Как быть?
Спасибо сказали:
Shurshunchik

Re: C++. Ошибка с инкрементом

Сообщение Shurshunchik »

И что в выводе тебя смущает? Выводим i=0, инкримируем i, делаем табуляцию, выводим i=1, инкримируем i.
Спасибо сказали:
Аватара пользователя
edoc_modnar
Бывший модератор
Сообщения: 1638
Статус: Форум больше не посещаю

Re: C++. Ошибка с инкрементом

Сообщение edoc_modnar »

Уууух, курить тебе не перекурить книги. Про префиксную и постфиксную форму инкремента/декремента знаем? Бегом в магазин :).
А "подразумеваемый" код выглядит так

Код: Выделить всё

#include <iostream>

int main() {
   int i=0;

   std::cout << ++i << "\t" << ++i << "\n";

   return 0;
}
So long, and thanks for all the fish.
Douglas Adams, The Hitchhiker's Guide to the Galaxy
Спасибо сказали:
Shurshunchik

Re: C++. Ошибка с инкрементом

Сообщение Shurshunchik »

std::cout << i++ - в начале выведет текущее значение i, а потом инкремирует его, а
std::cout << ++i - в начале инкремирует, а только потом выведет
Спасибо сказали:
Аватара пользователя
elide
Бывший модератор
Сообщения: 2421
Статус: Übermensch
ОС: лялих

Re: C++. Ошибка с инкрементом

Сообщение elide »

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

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

предлагаю дождаться oav'a. если я гоню, он поправит.


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

Код: Выделить всё

#include <iostream>
using namespace std;
struct func {
   func(int i){ (*this)(i); }
   func& operator()(int i){ mem(i); return *this; }
   void mem(int i){ cout << int(i); }
};

который позволяет делать вот так

Код: Выделить всё

int main(){
   int i=0;
   func(i++)(i++)(i++); // имхо, достаточно занятная конструкция (:
   cout << endl;
   func(1)(2)(3);
   cout << endl;
   func(++i)(++i)(++i);
   cout << endl;
   return 0;
}
слава роботам!
Спасибо сказали:
Аватара пользователя
edoc_modnar
Бывший модератор
Сообщения: 1638
Статус: Форум больше не посещаю

Re: C++. Ошибка с инкрементом

Сообщение edoc_modnar »

ребят, вы вообще поняли про что человек спросил?
как ваши посты с его вопросом вообще соотносятся?

да, действительно, не о том мы немного подумали :). признаю, ступил -- сказалась бессонница.
вообще, на моей системе этот код выдает 0 1, как и должно быть. gcc version 3.3.5-2005013, glibc-2.3.5, Gentoo. Глюк gcc 3.3.6, однозначно.
So long, and thanks for all the fish.
Douglas Adams, The Hitchhiker's Guide to the Galaxy
Спасибо сказали:
Ananas
Сообщения: 64

Re: C++. Ошибка с инкрементом

Сообщение Ananas »

Код: Выделить всё

(victor@pts/4)~ $> cat why.cpp                                            [sh]
#include <iostream>

int main()
{

     int i=0;

  std::cout << i++ << "\t" << i++ << "\n";

     return 0;
}

(victor@pts/4)~ $> g++ -o why why.cpp                                     [sh]
(victor@pts/4)~ $> ./why                                                  [sh]
0       1
(victor@pts/4)~ $> g++ --version                                          [sh]
g++ (GCC) 3.4.4
Copyright (C) 2004 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

(victor@pts/4)~ $>                                                        [sh]
Спасибо сказали:
Аватара пользователя
ilich
Сообщения: 156

Re: C++. Ошибка с инкрементом

Сообщение ilich »

(elide @ Суббота, 20 Августа 2005, 1:45) писал(а):вообще, я считаю, что это глюк gcc и прямое нарушение стандарта.
потому как вызов функции есть точка следования и к его началу все сайд-эффекты от вычисления операндов должны быть закреплены. gcc же, скатина, судя по асмовскому коду, сначала вычисляет аргументы и укладывает их в стек, а потом несколько раз подряд делает call.
естественно печататься они будут в обратном порядке.


Так же поступает и компилятор фирмы Microsoft.
!!! БЕРИ ОТ ЖИЗНИ ВСЕ !!!
Спасибо сказали:
Аватара пользователя
t.t
Бывший модератор
Сообщения: 7390
Статус: думающий о вечном
ОС: Debian, LMDE

Re: C++. Ошибка с инкрементом

Сообщение t.t »

2random_code, elide: Ещё один такой пост -- влеплю по плюшке каждому; разбираться, кто прав/виноват/"первый начал" не буду.


t.t добавил в 20.08.2005 13:39

А вообще, я вашу ругань покоцал ;)
¡иɯʎdʞ ин ʞɐʞ 'ɐнɔɐdʞǝdu qнεиж
Спасибо сказали:
Аватара пользователя
edoc_modnar
Бывший модератор
Сообщения: 1638
Статус: Форум больше не посещаю

Re: C++. Ошибка с инкрементом

Сообщение edoc_modnar »

Так же поступает и компилятор фирмы Microsoft.

но вот что интересно, и Visual C++ 6.0, и Visual C++ 7.0/7.1 выдают "0 1". И борландовский BC++ до кучи выдает то же самое :huh:
So long, and thanks for all the fish.
Douglas Adams, The Hitchhiker's Guide to the Galaxy
Спасибо сказали:
Аватара пользователя
innkeeper
Сообщения: 110

Re: C++. Ошибка с инкрементом

Сообщение innkeeper »

gcc и glibc менял на другие версии... то же самое... правда менял через swaret. Попробовать вручную собрать из исходников :-\
Спасибо сказали:
Аватара пользователя
edoc_modnar
Бывший модератор
Сообщения: 1638
Статус: Форум больше не посещаю

Re: C++. Ошибка с инкрементом

Сообщение edoc_modnar »

где гарантия, что с таким компилятором оно нормально соберется (если вообще соберется)? может стоит загрузить KNOPPIX и попробовать его компилятор? вдруг аппаратная проблема, чего в жизни не бывает? :huh:
So long, and thanks for all the fish.
Douglas Adams, The Hitchhiker's Guide to the Galaxy
Спасибо сказали:
neuralNetwork
Сообщения: 119
ОС: Debian Squeeze

Re: C++. Ошибка с инкрементом

Сообщение neuralNetwork »

(elide @ Суббота, 20 Августа 2005, 2:45) писал(а):вообще, я считаю, что это глюк gcc и прямое нарушение стандарта.
потому как вызов функции есть точка следования и к его началу все сайд-эффекты от вычисления операндов должны быть закреплены. gcc же, скатина, судя по асмовскому коду, сначала вычисляет аргументы и укладывает их в стек, а потом несколько раз подряд делает call.
естественно печататься они будут в обратном порядке.

IMHO, это не является нарушением стандарта. По крайней мере, в "Справочном руководстве по C++" Страустрапа в главе, посвященной операциям сказано следующее:
Приоритет операций в выражениях такой же, как и порядок главных подразделов в этом разделе [книги], наибольший приоритет у первого. Внутри каждого подраздела операции имеют одинаковый приоритет. В каждом подразделе для рассматриваемых в нем операций определяется их левая или правая ассоциативность (порядок обработки операндов).
В остальных случаях порядок вычисления выражения не определен. Точнее, компилятор волен вычислять подвыражения в том порядке, который он считает более эффективным, даже если подвыражения вызывают побочные эффекты. Порядок возникновения побочных эффектов не определен. Выражения, включающие в себя коммутативные и ассоциативные операции (*, +, &, |, ^), могут быть реорганизованы произвольным образом, даже при наличии скобок; для задания определенного порядка вычисления выражения необходимо использовать явную временную переменную.

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

P.S. Страустрап, конечно, не стандарт, но указанную вещь я встречал и в других местах (например, в своих лекциях по C с первого курса ;) ), так что склонен верить :)
Спасибо сказали:
Аватара пользователя
t.t
Бывший модератор
Сообщения: 7390
Статус: думающий о вечном
ОС: Debian, LMDE

Re: C++. Ошибка с инкрементом

Сообщение t.t »

(Neuron @ Суббота, 20 Августа 2005, 19:26) писал(а):Что касается вопроса о порядке выполнения разных ++ в приведенном выражении, то он действительно не определен и зависит от компилятора.
Нет, подождите. Унарная операция явлется частью выражения, т.е. в данном случае операндом операции <<. Если операция << выполняется справа налево, то и её операнды должны вычисляться в том же порядке.

У меня, кстати, вообще интересно. std::cout << i++ << "\t" << i++ << "\n"; выводит 0 1, а printf("%d\t%d\n",i++,i++); выводит 1 0.
¡иɯʎdʞ ин ʞɐʞ 'ɐнɔɐdʞǝdu qнεиж
Спасибо сказали:
neuralNetwork
Сообщения: 119
ОС: Debian Squeeze

Re: C++. Ошибка с инкрементом

Сообщение neuralNetwork »

(t.t @ Суббота, 20 Августа 2005, 21:10) писал(а):Нет, подождите. Унарная операция явлется частью выражения, т.е. в данном случае операндом операции <<. Если операция << выполняется справа налево, то и её операнды должны вычисляться в том же порядке.

Ассоциативность определяет лишь то, что в выражении std::cout << выр1 << выр2 сначала будет вычислено std::cout << выр1, а затем то, (что получится типа ostream&) << выр2, а не начнет с вычисления выр1 << выр2. При этом не определяется, как будут вычисляться сами выр1 и выр2. На самом деле, все даже еще хуже, чем казалось. То, что порядок возникновения побочные эффектов не определен даже при наличии скобок, по видимому, означает, что компилятор может вычислять выражения отдельно, а побочные эффекты создавать отдельно. Казалось бы, что в выражении

Код: Выделить всё

(((std::cout << i++) << "\t") << i++) << "\n";

четко определено, что сначала выполняется ++ в первых скобках, затем <<, к полученному ostream& применяется << и т. д.
На самом деле, на моей машине, и код без скобок, и код со скобками дает 1 0!
Спасибо сказали:
Аватара пользователя
t.t
Бывший модератор
Сообщения: 7390
Статус: думающий о вечном
ОС: Debian, LMDE

Re: C++. Ошибка с инкрементом

Сообщение t.t »

Ну.. Вы ведь сами пишете:
(Neuron @ Суббота, 20 Августа 2005, 21:38) писал(а):сначала будет вычислено std::cout << выр1
(Neuron @ Суббота, 20 Августа 2005, 21:38) писал(а):а затем то, (что получится типа ostream&) << выр2
Тогда в какой бы последовательности ни вычислялись инкременты, мы не должны были бы получить на выходе или 0 1, или 1 2, но никак не 1 0. Вообще, я уже слегка запутался. Неплохо бы асм глянуть, чтобы понять, почему так происходит, но я пока в атт-шном синтаксисе не силён.
¡иɯʎdʞ ин ʞɐʞ 'ɐнɔɐdʞǝdu qнεиж
Спасибо сказали:
Аватара пользователя
innkeeper
Сообщения: 110

Re: C++. Ошибка с инкрементом

Сообщение innkeeper »

я этот глюк нашёл, кстати говоря, в такой записи ;-)

sscanf(buffer, "%e,%e,%e,", noco[i++], noco[i++], noco[i++]);

А такая запись всё равно не помогла:

(((std::cout << i++) << "\t") << i++) << "\n";
Спасибо сказали:
neuralNetwork
Сообщения: 119
ОС: Debian Squeeze

Re: C++. Ошибка с инкрементом

Сообщение neuralNetwork »

(Neuron @ Суббота, 20 Августа 2005, 21:38) писал(а):сначала будет вычислено std::cout << выр1
(Neuron @ Суббота, 20 Августа 2005, 21:38) писал(а):а затем то, (что получится типа ostream&) << выр2

Но выр1 и выр2 могут вычисляться совершенно независимо от всей вышеозначенной последовательности <<, а все скобки компилятором могут игнорироваться. Ведь если мы имеем нормальное выражение без сторонних эффектов, например,

Код: Выделить всё

(((std::cout << i++) << "\t") << j++) << "\n";

то мы можем посчитать его так, как я написал выше и получим правильный результат.
Вообще, похоже на то, что поскольку
(Страустрап) писал(а):Порядок возникновения побочных эффектов не определен

разработчики компиляторов работают с выражениями так, как если бы сторонних эффектов не было бы вовсе.
А программистам остается только не использовать в выражении более одного раза переменную, значение которой изменяется в процессе вычисления этого выражения!
Спасибо сказали:
Аватара пользователя
t.t
Бывший модератор
Сообщения: 7390
Статус: думающий о вечном
ОС: Debian, LMDE

Re: C++. Ошибка с инкрементом

Сообщение t.t »

Это я всё понимаю. Но всё же интересно было бы, чтобы кто-то глянул это всё на уровне асма.
¡иɯʎdʞ ин ʞɐʞ 'ɐнɔɐdʞǝdu qнεиж
Спасибо сказали:
Аватара пользователя
elide
Бывший модератор
Сообщения: 2421
Статус: Übermensch
ОС: лялих

Re: C++. Ошибка с инкрементом

Сообщение elide »

вызов функции есть точка следования и к его началу все сайд-эффекты от вычисления операндов должны быть закреплены. gcc же, скатина, судя по асмовскому коду, сначала вычисляет аргументы и укладывает их в стек, а потом несколько раз подряд делает call.
слава роботам!
Спасибо сказали:
Аватара пользователя
t.t
Бывший модератор
Сообщения: 7390
Статус: думающий о вечном
ОС: Debian, LMDE

Re: C++. Ошибка с инкрементом

Сообщение t.t »

2elide: О, спасибо. Не заметил тот твой пост.
¡иɯʎdʞ ин ʞɐʞ 'ɐнɔɐdʞǝdu qнεиж
Спасибо сказали:
Аватара пользователя
oav
Бывший модератор
Сообщения: 296

Re: C++. Ошибка с инкрементом

Сообщение oav »

Для точности могу привети цитату из стандарта:

Except where noted, the order of evaluation of operands of individual operators and subexpressions of individual
expressions, and the order in which side effects take place, is unspecified.53) Between the previous
and next sequence point a scalar object shall have its stored value modified at most once by the evaluation
of an expression. Furthermore, the prior value shall be accessed only to determine the value to be stored.
The requirements of this paragraph shall be met for each allowable ordering of the subexpressions of a full
expression; otherwise the behavior is undefined. [Example:
i = v[i++]; // the behavior is unspecified
i = 7, i++, i++; // i becomes 9
i = ++i + 1; // the behavior is unspecified
i = i + 1; // the value of i is incremented
—end example]


От себя добавлю: играть с такими конструкциями - рыть себе яму из неуловимых в будущем багов и бессонных ночей, undefined behavior в С++ реально много, и чем проще вы пишете код, тем вам же легче его сопровождать :)

и еще по поводу конструкции stream << expr << expr2 << ...

Это трюк С++, хотя кажется что это одно выражение. Оператор << для std::*stream возвращает ссылку на сам стрим, т.е. вся эта конструкция замечательная есть обычный последовательный вызов функции (точнее перегруженных операторов <<):
((stream)(stream << expr)) << expr2
Спасибо сказали:
Аватара пользователя
elide
Бывший модератор
Сообщения: 2421
Статус: Übermensch
ОС: лялих

Re: C++. Ошибка с инкрементом

Сообщение elide »

вся эта конструкция замечательная есть обычный последовательный вызов функции
я в курсе. именно поэтому я и не понимаю нихрена. потому как вызов функции и есть точка следования, а i между вызовами меняется не более 1 раза, т.е. либо эта конструкция должна работать правильно, либо я где-то ошибаюсь, но не могу понять где (:
слава роботам!
Спасибо сказали:
Shurshunchik

Re: C++. Ошибка с инкрементом

Сообщение Shurshunchik »

Как получить ассемблерный листинг кода? Может ли gcc дать мне ассмовский файл?
Я задал сабж емы на руборде. Посмотрим, что ответят. :)
Спасибо сказали:
Аватара пользователя
oav
Бывший модератор
Сообщения: 296

Re: C++. Ошибка с инкрементом

Сообщение oav »

(elide @ Понедельник, 22 Августа 2005, 12:03) писал(а):
вся эта конструкция замечательная есть обычный последовательный вызов функции
я в курсе. именно поэтому я и не понимаю нихрена. потому как вызов функции и есть точка следования, а i между вызовами меняется не более 1 раза, т.е. либо эта конструкция должна работать правильно, либо я где-то ошибаюсь, но не могу понять где (:


имхо ноги этого ub растут от реализации компиляторов. т.к. ведь данное выражение тоже не вызывает не определенности для нас?
i = ++i + 1; // the behavior is unspecified

1) увеличить значение i
2) используя увеличенное значение i прибавить к нему 1
3) результат 1 и 2 сохранить в i.

the behavior is unspecified. subexpression (т.е. ++i) должно быть зафиксировано

:wacko:
Спасибо сказали:
Аватара пользователя
t.t
Бывший модератор
Сообщения: 7390
Статус: думающий о вечном
ОС: Debian, LMDE

Re: C++. Ошибка с инкрементом

Сообщение t.t »

(Shurshunchik @ Понедельник, 22 Августа 2005, 11:40) писал(а):Как получить ассемблерный листинг кода? Может ли gcc дать мне ассмовский файл?
-S
Кстати, я узнал об этом ключе после 15 секунд поиска по man gcc.
¡иɯʎdʞ ин ʞɐʞ 'ɐнɔɐdʞǝdu qнεиж
Спасибо сказали:
Shurshunchik

Re: C++. Ошибка с инкрементом

Сообщение Shurshunchik »

Кстати, я узнал об этом ключе после 15 секунд поиска по man gcc.

У меня под рукой просто манов нету.

Код: Выделить всё

.file    "main.cpp"
    .version    "01.01"
gcc2_compiled.:
.globl __terminate
.globl __sjthrow
    .section    .rodata
.LC0:
    .byte  0xa,0x0
.LC1:
    .byte  0x9,0x0
.text
    .p2align 2,0x90
.globl main
  .type   main,@function
main:
    pushl %ebp
    movl %esp,%ebp
    subl $24,%esp
    movl $0,-4(%ebp)
    addl $-8,%esp
    pushl $.LC0
    addl $-8,%esp
    movl -4(%ebp),%eax
    pushl %eax
    incl -4(%ebp)
    addl $-8,%esp
    pushl $.LC1
    addl $-8,%esp
    movl -4(%ebp),%eax
    pushl %eax
    incl -4(%ebp)
    pushl $cout
    call __ls__7ostreami
    addl $16,%esp
    movl %eax,%eax
    pushl %eax
    call __ls__7ostreamPCc
    addl $16,%esp
    movl %eax,%eax
    pushl %eax
    call __ls__7ostreami
    addl $16,%esp
    movl %eax,%eax
    pushl %eax
    call __ls__7ostreamPCc
    addl $16,%esp
    xorl %eax,%eax
    jmp .L275
    xorl %eax,%eax
    jmp .L275
    .p2align 2,0x90
.L275:
    leave
    ret
.Lfe1:
  .size   main,.Lfe1-main
    .ident    "GCC: (GNU) cplusplus 2.95.4 20020320 [FreeBSD]"


Shurshunchik добавил в 22.08.2005 15:24

Вот что ответил мне знающий чел:
при обработке строки

std::cout << i++ << "\t" << i++ << "\n"

компилятор вначале генерит стек для 4-х вызовов вывода в COUT  а потом последовательно их вызывает т.к. стек надо генерить с конца т.е. чтобы вывод "\n" был последним его данные надо запихнуть первыми
вот он и делает
запихивает в стек "\n"
запихивает в стек i
делает i++
запихивает в стек "\t"
запихивает в стек i
делает i++

потом начинаются вызываться выводы в Cout
Спасибо сказали:
Andrew S
Сообщения: 225
Статус: экспериментатор
ОС: Conrad-Gentoo

Re: C++. Ошибка с инкрементом

Сообщение Andrew S »

Поиграйтесь с опциями -W (при включенной -Werror естественно). Компилятор ругнется и будет прав. Я тоже плевался, когда переделывал чужую программу с описанными выше конструкциями.
Спасибо сказали: