operator inkrementacji w C# i C++

0

Ten sam program daje w zależności od języka wynik 39 w C++ 36 w C#.
Który waszym zdaniem sposób liczenia jest lepszy ? Z czego to wynika ?

using System;

namespace ConsoleApp16
{
    class Program
    {
        static void Main(string[] args)
        {
            int a = 10;
            int wynik = ++a + ++a + ++a;
            Console.WriteLine(wynik);
        }
    }
}
#include "pch.h"

using namespace System;

int main(array<System::String ^> ^args)
{
    
    int a = 10;
    int wynik = ++a + ++a + ++a;
    Console::WriteLine(wynik);

}
0

Po co w ogole tak pisac

0

Ale to nie jest prawdziwy C++ tylko C++.NET

1

Moim zdaniem, powinno być 36, bo w tym kawałku kodu a ma wartość 10, a potem następuje trzykrotna inkrementacja z podstawieniem, czyli int wynik = ++a + ++a + ++a; powinno być zamienione na int wynik = 11 + 12 + 13;.

Bardzo możliwe, że nie ma przyjętego jednoznacznego sposobu interpretacji tego zapisu i jest to zachowanie niezdefiniowane.

Jeszcze możliwa jest interpretacja dająca wynik 33 w ten sposób, że kompilator wyczuje, że int wynik = ++a + ++a + ++a; to jest to samo, co int wynik = t + t + t;, a t = ++a;, więc po podstawieniu wyjdzie int wynik = 11 + 11 + 11;.

0

Należałoby zdezasemblować binarki i zobaczyć jak wygląda kod IL.

0

Tutaj masz C++ i C#

.method assembly static int32  main(string[] args) cil managed
{
  // Code size       29 (0x1d)
  .maxstack  2
  .locals ([0] int32 a,
           [1] int32 wynik)
  IL_0000:  ldc.i4.s   10
  IL_0002:  stloc.0
  IL_0003:  ldloc.0
  IL_0004:  ldc.i4.1
  IL_0005:  add
  IL_0006:  stloc.0   
  IL_0007:  ldloc.0
  IL_0008:  ldc.i4.1
  IL_0009:  add
  IL_000a:  stloc.0  
  IL_000b:  ldloc.0
  IL_000c:  ldc.i4.1
  IL_000d:  add
  IL_000e:  stloc.0 
  IL_000f:  ldloc.0
  IL_0010:  dup
  IL_0011:  add
  IL_0012:  ldloc.0
  IL_0013:  add  
  IL_0014:  stloc.1
  IL_0015:  ldloc.1
  IL_0016:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_001b:  ldc.i4.0
  IL_001c:  ret
} // end of method 'Global Functions'::main
.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       30 (0x1e)
  .maxstack  3
  .locals init ([0] int32 a,
           [1] int32 wynik)
  IL_0000:  nop
  IL_0001:  ldc.i4.s   10
  IL_0003:  stloc.0
  IL_0004:  ldloc.0
  IL_0005:  ldc.i4.1
  IL_0006:  add
  IL_0007:  dup
  IL_0008:  stloc.0
  IL_0009:  ldloc.0
  IL_000a:  ldc.i4.1
  IL_000b:  add
  IL_000c:  dup
  IL_000d:  stloc.0
  IL_000e:  add
  IL_000f:  ldloc.0
  IL_0010:  ldc.i4.1
  IL_0011:  add
  IL_0012:  dup
  IL_0013:  stloc.0
  IL_0014:  add
  IL_0015:  stloc.1
  IL_0016:  ldloc.1
  IL_0017:  call       void [mscorlib]System.Console::WriteLine(int32)
  IL_001c:  nop
  IL_001d:  ret
} // end of method Program::Main
0
    int wynik = 0;
    int  a = 10;
    wynik = ++a + ++a + ++a;

C++, zgodnie z definicja "pre increment": Zanim wykonane jest przypisanie, zmienna jest inkrementowana o jeden; czyli, są dwa dodawania i trzy "pre inkrementacje" -
najpierw, ++a - a = 11 ;
teraz, inkrementujemy, a i dodajemy, 12 + 14, (pre inkrementacja);
finalnie, 11 + 12 + 14 = 37 - w g++ 9.3.0;
w jakim kompilatorze Ci wyszło 36?
A tak:

    int wynik = 0;
    int  a = 10;
    wynik = ++a + (++a + ++a);

    cout << wynik <<"\n";   // -> 39

Najpierw:
11 + (12 + 13);
teraz, przed kolejnym przypisaniem, pre inkrementacja:
11 + ( 13 + 14)
i pre inkrementacja:
12 + (13 + 14) = 39
Czyli C#jakoś inaczej parsuje niż C++

1
lion137 napisał(a):

C++, zgodnie z definicja "pre increment": Zanim wykonane jest przypisanie, zmienna jest inkrementowana o jeden; czyli, są dwa dodawania i trzy "pre inkrementacje" -

Jest gdzieś taka definicja pre-increment w C++ publicznie dostępna i zaakceptowana jako jakiś standard? Bo bardzo wątpię...

W C# jest prosto - operator preinkrementacji zwraca zmieniony operand, więc wynik = ++a + ++a + ++a; oznacza: wynik = (10 + 1) + ((10 + 1) + 1) + (((10 + 1) + 1) + 1); (nawiasami otoczyłem oczywiście wyniki poprzednich inkrementacji.

W C++ zapewne definicji nie ma, więc co sobie kompilator wylosuje, to będzie. W tym przypadku najwyraźniej są robione najpierw 3 inkrementacje, a potem po prostu dodawane są trzy te same liczby. Wat.

najpierw, ++a - a = 11 ;
teraz, inkrementujemy, a i dodajemy, 12 + 14, (pre inkrementacja);
finalnie, 11 + 12 + 14 = 37 - w g++ 9.3.0;

No to to ma jeszcze mniej sensu niż 39. Ale znowu - to w końcu C++, więc implementation specific undefinded behaviour is the king.

1

W C++ taka operacja jest undefined behavior, więc nie ma o czym dyskutować.

0

@somekind: 39 ma według mnie największy sens bo to odpowiada takim instrukcjom :
++a;
++a;
++a;
wynik = a + a + a;
Nie każdy dostrzega że mamy jedną zmienną
Sprawdzałem na kilku kompilatorach i zawsze wychodzi 39.
Natomiast w C# jest tak :
wynik = (a + 1 ) + ( a + ( 1 + 1)) + (a + (1 + 1 + 1)); - tak jakbyśmy mieli kilka instancji tej samej zmiennej i każda ma inną wartość a w C++ zmienna a jest traktowana statycznie
Wniosek jest taki że lepiej nie zmieniać więcej niż 1 raz wartości tego samego operanda w jednym wyrażeniu bo każdy kompilator może liczyć inaczej.
To nie znaczy też jak inni sugerują że kompilator może policzyć cokolwiek jak funkcja Random . Po prostu nie wszystkie reguły są podawane w specyfikacjach

1

@Zimny Krawiec: najwyraźniej nie zawsze 39: https://godbolt.org/z/ax1jYe
O C# się nie wypowiem bo nie znam się na nim ponad rozumienie podstaw, ale w temacie C++ Twój post wprowadza w błąd.
To jest UB, tłumaczono Ci to wcześniej. Raz jeszcze: kompilator może zrobić cokolwiek (tu specyfikacja się kończy), typowo zachowa się tak, żeby wygenerować optymalny kod ale nie musi być to nijak przewidywalne powtarzalne i błędnie rozumujesz, że to nie jest podane w specyfikacji.

3
Zimny Krawiec napisał(a):

To nie znaczy też jak inni sugerują że kompilator może policzyć cokolwiek jak funkcja Random . Po prostu nie wszystkie reguły są podawane w specyfikacjach

Tu masz w standardzie języka podane, że to jest undefined behavior, czyli kompilator może iść i usunąć wszystko z dysku. Jak masz UB, to program jest niepoprawny. Jeżeli nie wiesz, co to jest UB, to nie zbliżaj się do C++, to nie jest "to nie jest zdefiniowane przez standard" tylko "to jest zdefiniowane przez standard jako zachowanie niezdefiniowane".

Zimny Krawiec napisał(a):

Nie zgodzę się z tym ale ci tego teraz ci tego nie udowodnię. Optymalizacja nie powoduje tego że otrzymamy inny wynik tylko np. że coś będzie działo szybciej albo nie będzie się niepotrzebnie powtarzać

Optymalizacja może zmienić wynik, może spowodować cofanie się w czasie albo inne nieogarnialne rzeczy. https://devblogs.microsoft.com/oldnewthing/20140627-00/?p=633

2

@Zimny Krawiec: nie udowodnisz bo nie masz racji:

[C++14: defns.undefined]: [..] Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message). [..]

https://www.nayuki.io/page/undefined-behavior-in-c-and-cplusplus-programs
To o czym mówisz to jest implementation defined i to jest coś innego. Ale nie ma zastosowania w tym przypadku.

1

@Zimny Krawiec:
Wszelkie rozważania na temat tego, jak powinien działać program w przypadku w prowadzenia do niego undefined behavior nie mają sensu, ponieważ według specyfikacji C++ "[Undefined behavior] renders the entire program meaningless".
Dziękuje, do widzenia, zagadka rozwiązana, koniec tematu.

0

@Aterwik: W innych językach jest inaczej niż w C++ ? Moim zdaniem jest tak samo .

0
Zimny Krawiec napisał(a):

@Aterwik: W innych językach jest inaczej niż w C++ ? Moim zdaniem jest tak samo .

Obstawiam, że to raczej C++ jest niechlubnym wyjątkiem, a reszta popularnych języków ma wyspecyfikowane działanie pre- i post-inkrementacji.

Sprawdziłem działanie:

var a = 0
wypisz(++a + ++a + ++a)

Na C#, Javie i JS i wszystkie wypisują 6. Nie chciało mi się sprawdzać tego twierdzenia o specyfikacji.

Ciekawostka. Co wypisze podany kod w C++:

#include <iostream>

void printer(int a, int b, int c) {
  std::cout << a << " " << b << " " << c << std::endl;
}

int main() {
  int a = 0;
  printer(a++, a++, a++);
  return 0;
}

Odpowiedź:
Pod GCC wypisze: 2 1 0
Pod clangiem wypisze: 0 1 2 (i rzygnie ostrzeżeniem podczas kompilacji: warning: multiple unsequenced modifications to 'a' [-Wunsequenced])

0

@Aterwik: Skoro tak wszystko wiesz to powiedz mi , czy na poziomie asemblera albo kodu maszynowego też możemy mieć niezdefiniowane zachowanie ?

0
Zimny Krawiec napisał(a):

@Aterwik: Skoro tak wszystko wiesz to powiedz mi , czy na poziomie asemblera albo kodu maszynowego też możemy mieć niezdefiniowane zachowanie ?

Można mieć, np:
https://www.felixcloutier.com/x86/bsf
https://www.felixcloutier.com/x86/bsr

Poza tym masa instrukcji x86 pozostawia flagi procesora w stanie niezdefiniowanym.

0
Afish napisał(a):

Tu masz w standardzie języka podane, że to jest undefined behavior,

Co nie znaczy że to jest dobrze.
Uważam że to jest niedobrze, i takich UB nie powinno być w języku. W przytoczonym C# takie wyrażenia z ++ i -- mają zawsze zdefiniowany wynik o ile się kompilują (niektóre przypadki UB z C++ się w C# nie skompilują).
Kiedyś potrafiłem z głowy policzyć ile to będzie ++a + ++a + ++a w C#, teraz już mi się nie chce głowy zaśmiecać zbędnymi informacjami.

0
Azarien napisał(a):

Co nie znaczy że to jest dobrze.
Uważam że to jest niedobrze, i takich UB nie powinno być w języku

Zgadzam się.

0

Kilka lat temu wyczytałem na tym forum właśnie w dziale c++, że ++n najpierw zmienia wartość zmiennej n a potem ją zwraca. Tak więc według mnie najrozsądniejszą analizą byłoby:

int n=10;
n = ++n + ++n
//++n: n = 11
//+
//++n: n = 12
//n = 23

Natomiast:

n = n++ + n++
//n++: n = 10, po zwróceniu liczby 11
//+
//n++: n = 11, po zwróceniu liczby 12
//n = 21

Dobrze to rozumuję?

0
Azarien napisał(a):
Afish napisał(a):

Tu masz w standardzie języka podane, że to jest undefined behavior,

Co nie znaczy że to jest dobrze.

A według mnie dobrze. Jak ktoś tworzy kwiatki w stylu int wynik = ++a + ++a + ++a;, to niech potem ma karę podczas debugowania. To tak trochę pół żartem, pół serio.

0
Zimny Krawiec napisał(a):

@somekind: 39 ma według mnie największy sens bo to odpowiada takim instrukcjom :

++a;
++a;
++a;
wynik = a + a + a;

Miałoby to sens, gdyby w specyfikacji było napisane, że najpierw wykonywane są wszystkie preinkrementacje, a potem wszystkie dodawania. Jeśli nie zostało to tak nigdzie zdefiniowane, to jest to wynik równie dobry jak każdy inny. Aczkolwiek dość nieintuicyjne byłoby zachowanie łamiące naturalną kolejność działań i precedens operatorów.

Natomiast w C# jest tak :
wynik = (a + 1 ) + ( a + ( 1 + 1)) + (a + (1 + 1 + 1)); - tak jakbyśmy mieli kilka instancji tej samej zmiennej i każda ma inną wartość

To wynika z opisanego w specyfikacji działania operatora preinkrementacji, który zwraca zmienną po zmianie oraz faktu, że dodawanie wykonywane jest od lewej do prawej, a więc najpierw zwiększamy, potem dodajemy znowu zwiększoną, a na końcu do sumy tych dwóch dodajemy zwiększoną trzeci raz.

Wniosek jest taki że lepiej nie zmieniać więcej niż 1 raz wartości tego samego operanda w jednym wyrażeniu bo każdy kompilator może liczyć inaczej.

W C++ tak. W językach, w których jest to precyzyjnie zdefiniowane problemu nie ma.

0

@stivens: A spróbuj np taki przykład:

int[] tab = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }
int n = 0;
Console.WriteLine(tab[n++]);
n = 0;
Console.WriteLine(tab[++n]);

Wynik:
0
1

Moje działania nie są bełkotem. Przy pierwszym Console.WriteLine zwróci Ci 0, ponieważ dopiero po zwróceniu aktualnej wartości zmienia ją o 1

0

n++ ma wartosc n ale po wykonaniu operacji zachodzi n = poczatkowa wartosc + 1
++n ma wartosc n+1 ale w obu przypadkach rejestr zawierajacy pamiec podpisana jako n ma dokladnie taka sama zawartosc.

A ty piszesz:

int n=10;
//n++: n = 10, po zwróceniu liczby 11

No nie...


Zakladam ze myslisz moze i dobrze ale to co napisales to belkot.

0

"n++ ma wartosc n ale po wykonaniu operacji zachodzi n = początkowa wartosc + 1" - Tak właśnie to rozumuję, może źle to opisałem.

0
stivens napisał(a):

n++ ma wartosc n ale po wykonaniu operacji zachodzi n = poczatkowa wartosc + 1

++n ma wartosc n+1 ale w obu przypadkach rejestr zawierajacy pamiec podpisana jako n ma dokladnie taka sama zawartosc.

Rejestry to jedno, a drugie to to, co zwraca operator i co jest użyte jako składnik dodawania.

1

@Zimny Krawiec:

39 ma według mnie największy sens bo to odpowiada takim instrukcjom :
++a;
++a;
++a;
wynik = a + a + a;
Nie każdy dostrzega że mamy jedną zmienną
Sprawdzałem na kilku kompilatorach i zawsze wychodzi 39.
Natomiast w C# jest tak :
wynik = (a + 1 ) + ( a + ( 1 + 1)) + (a + (1 + 1 + 1)); - tak jakbyśmy mieli kilka instancji tej samej zmiennej i każda ma inną wartość a w C++ zmienna a jest traktowana statycznie
Wniosek jest taki że lepiej nie zmieniać więcej niż 1 raz wartości tego samego operanda w jednym wyrażeniu bo każdy kompilator może liczyć inaczej.
To nie znaczy też jak inni sugerują że kompilator może policzyć cokolwiek jak funkcja Random . Po prostu nie wszystkie reguły są podawane w specyfikacjach

nie wiem, jak się patrzy na drzewko to wynik 36 ma dużo sensu

\-CompilationUnitSyntax
|-UsingDirectiveSyntax
| \-IdentifierNameSyntax System
\-ClassDeclarationSyntax
\-MethodDeclarationSyntax
  |-PredefinedTypeSyntax
  |-ParameterListSyntax
  \-BlockSyntax
	|-LocalDeclarationStatementSyntax
	| \-VariableDeclarationSyntax
	|   |-PredefinedTypeSyntax
	|   \-VariableDeclaratorSyntax
	|     \-EqualsValueClauseSyntax
	|       \-LiteralExpressionSyntax
	|-LocalDeclarationStatementSyntax
	| \-VariableDeclarationSyntax
	|   |-PredefinedTypeSyntax
	|   \-VariableDeclaratorSyntax
	|     \-EqualsValueClauseSyntax
	_______________________________________________________
	|       \-BinaryExpressionSyntax
	|         |-BinaryExpressionSyntax
	|         | |-PrefixUnaryExpressionSyntax ++ 
	|         | | \-IdentifierNameSyntax a (makes 11)
	|         | \-PrefixUnaryExpressionSyntax ++ 
	|         |   \-IdentifierNameSyntax a (makes 12)
	|         \-PrefixUnaryExpressionSyntax ++
	|           \-IdentifierNameSyntax a (makes 13)
	_______________________________________________________
	\-ExpressionStatementSyntax
	  \-InvocationExpressionSyntax
		|-MemberAccessExpressionSyntax
		| |-IdentifierNameSyntax Console
		| \-IdentifierNameSyntax WriteLine
		\-ArgumentListSyntax
		  \-ArgumentSyntax
			\-IdentifierNameSyntax wynik

1 użytkowników online, w tym zalogowanych: 0, gości: 1