Co jest nie tak z moim przykładowym kodem?

1

W innym wątku (https://4programmers.net/Forum/Kosz/364527-kod_sie_nie_kompiluje?p=1876412#id1876412) popełniłem następujący przykładowy kod:

#include <stdio.h>

int main(void)
{
	int i;

	for (i = 0; i < 10; i++)
		printf("%i\n", i);
	return 0;
}

Kolega @_13th_Dragon natychmiast wytknął mi, że w tych 10 wierszach trzykrotnie złamałem dobre praktyki, nie wskazując jednak jakie.

No właśnie: jakie?

3

Jakbyś w ostatnie 20 lat zaktualizował kompilator to byś miał tam wsparcie dla C99, w którym możesz pisać

#include <stdio.h>

int main(void)
{
    for (int i = 0; i < 10; i++) {
        printf("%i\n", i);
    }
    return 0;
}

oczywiście nie ma co się rozwodzić nad dobrymi praktykami w takim kodzie, bo i tak ten kod zaraz odejdzie w zapomnienie.

2
pifko napisał(a):

W innym wątku (https://4programmers.net/Forum/Kosz/364527-kod_sie_nie_kompiluje?p=1876412#id1876412) popełniłem następujący przykładowy kod:

#include <stdio.h>

int main(void)
{
	int i;

	for (i = 0; i < 10; i++)
		printf("%i\n", i);
	return 0;
}

Kolega @_13th_Dragon natychmiast wytknął mi, że w tych 10 wierszach trzykrotnie złamałem dobre praktyki, nie wskazując jednak jakie.

No właśnie: jakie?

Na tak małym przykładzie to na prawdę nie ma co się rozdrabniać, tu prawie nie ma logiki. Jeśli chcesz być pedantyczny, to mi się wydaje że chodzi o to że deklaracja int i pewnie mogłaby być w ciele pętli, że jest i++ zamiast ++i, oraz pewnie o to że nie ma bracketów dookoła tego printf(). Możliwe że jednym z nich też były taby, które poprawiłes. Ale to na prawdę nie ma znaczenia, zajmij się programowaniem dłużej, napisz coś większego, wtedy możesz pokazać kod do oceny.

Poza tym, tak na prawdę to przywołujesz słowa @_13th_Dragon wyjęte z kontekstu, cała rozmowa jest tutaj: https://4programmers.net/Forum/Kosz/364527-kod_sie_nie_kompiluje?p=1876412#id1876412 i nie sądzę że to dobry pomysł żeby tak wybrać pojedynczy komentarz od @_13th_Dragon dragon i go tak "analizować". Moim zdaniem, powinieneś przywołać całą rozmowę.

0
enedil napisał(a):

Jakbyś w ostatnie 20 lat zaktualizował kompilator to byś miał tam wsparcie dla C99, w którym możesz pisać

Przeniosłeś deklarację int i, a przecież to jest C - przyjmuje się, że zmienne lokalne powinny być deklarowane na początku funkcji (nie w wewnętrznych blokach, nie w for, itd.) - chyba, że stopień skomplikowania funkcji tego wymaga.

Jeszcze main(void) - to już zamierzchłe czasy. — _13th_Dragon

Jakie "zamierzchłe czasy", przecież nawet w C17/C18 jedna z dwóch alternatywnych definicji ma następującą postać (5.1.2.2.1 Program startup):

int main(void) { /* ... */ }

Podobnie i++ vs ++i - standard C17/C18 we wszystkich przykładach inkrementacji iteratora pętli ma i++ (nie ++i) :D

Sprawy mają się podobnie w C2X - najwyraźniej pewne rzeczy są ponadczasowe ;)

2
pifko napisał(a):
enedil napisał(a):

Jakbyś w ostatnie 20 lat zaktualizował kompilator to byś miał tam wsparcie dla C99, w którym możesz pisać

Przeniosłeś deklarację int i, a przecież to jest C - przyjmuje się, że zmienne lokalne powinny być deklarowane na początku funkcji (nie w wewnętrznych blokach, nie w for, itd.) - chyba, że stopień skomplikowania funkcji tego wymaga.

Bzdura.

Podobnie i++ vs ++i - standard C17/C18 we wszystkich przykładach inkrementacji iteratora pętli ma i++ (nie ++i) :D

No a wiesz że i++ trzyma dwa słowa 4-bajtowe w pamięci, a ++i jedno? Bo i++ musi zapamiętać starą wartość zeby ją zwrócić przed zmianą; a ++i po prostu zwiększa wartość? i++ jest tak na prawdę równe dwóm zmiennym.

0
Riddle napisał(a):

Bzdura.

Kolega mija się z prawdą :(

No a wiesz że i++ trzyma dwa słowa 4-bajtowe w pamięci, a ++i jedno? Bo i++ musi zapamiętać starą wartość zeby ją zwrócić przed zmianą; a ++i po prostu zwiększa wartość? i++ jest tak na prawdę równe dwóm zmiennym.

Kolega ponownie mija się z prawdą :(

2

Podobnie i++ vs ++i - standard C17/C18 we wszystkich przykładach inkrementacji iteratora pętli ma i++ (nie ++i) :D
Sprawy mają się podobnie w C2X - najwyraźniej pewne rzeczy są ponadczasowe ;)

w teorii ++i może być szybsze niż i++. Tyle że te rozważania są bez sensowne gdyż będzie optymalizacja po stornie kompilatora. i to jest od bardz oale to bardzo długiego czasu,chociaż po poradach odnośnie mikroprocków jeszcze o tym piszą.

0
revcorey napisał(a):

Podobnie i++ vs ++i - standard C17/C18 we wszystkich przykładach inkrementacji iteratora pętli ma i++ (nie ++i) :D
Sprawy mają się podobnie w C2X - najwyraźniej pewne rzeczy są ponadczasowe ;)

w teorii ++i może być szybsze niż i++. Tyle że te rozważania są bez sensowne gdyż będzie optymalizacja po stornie kompilatora. i to jest od bardz oale to bardzo długiego czasu,chociaż po poradach odnośnie mikroprocków jeszcze o tym piszą.

Ja tu nie przyszedłem dywagować o tym które i++ vs ++i jest faktycznie szybsze, tylko odpowiedzieć na Twoje zadane pytanie:

pifko napisał(a):

Kolega @_13th_Dragon natychmiast wytknął mi, że w tych 10 wierszach trzykrotnie złamałem dobre praktyki, nie wskazując jednak jakie.

No właśnie: jakie?

Dostałeś już odpowiedź, jakie. Jeśli chcesz z tym polemizować, to proponuję wrócić do początkowego tematu: https://4programmers.net/Forum/Kosz/364527-kod_sie_nie_kompiluje?p=1876412#id1876412, bo to tam jest całe sedno.

1

Ja tu nie przyszedłem dywagować o tym które i++ vs ++i jest faktycznie szybsze, tylko odpowiedzieć na Twoje zadane pytanie:

Ale ja żadnych pytań nie zadawałem.... tylko wyjaśniłem dlaczego cała dywagacja na temat i++ i ++i jest dziś zbędna.

2
pifko napisał(a):

Przeniosłeś deklarację int i, a przecież to jest C - przyjmuje się, że zmienne lokalne powinny być deklarowane na początku funkcji (nie w wewnętrznych blokach, nie w for, itd.) - chyba, że stopień skomplikowania funkcji tego wymaga.>

Bzdura. Nawet kernel wreszcie przesiadł się na C11 z ANSI C, tylko po to żeby nie trzeba było tego świństwa wyprawiać.

0
revcorey napisał(a):

w teorii ++i może być szybsze niż i++. Tyle że te rozważania są bez sensowne gdyż będzie optymalizacja po stornie kompilatora. i to jest od bardz oale to bardzo długiego czasu,chociaż po poradach odnośnie mikroprocków jeszcze o tym piszą.

To jest mit wynikający prawdopodobnie z C++, w którym operator ++ może być przeciążony i zdefiniowany w klasie.

Natomiast w C jeżeli nie używamy wartości wyrażenia i++, to nawet bez włączonych optymalizacji w praktyce dostaniemy taki sam kod niezależnie wariantu i++ lub ++i.

Co innego, jeżeli interesuje nas wartość wyrażenia, ale wtedy zazwyczaj nie mamy swobody wyboru pomiędzy i++ a ++i.

0
enedil napisał(a):

Bzdura. Nawet kernel wreszcie przesiadł się na C11 z ANSI C, tylko po to żeby nie trzeba było tego świństwa wyprawiać.

Jaki kernel? Chyba Linux, rzeczywiście oni tam w kodzie różne rzeczy wyprawiają, np. mają to swoje RCU i takie tam :)

3
pifko napisał(a):

przyjmuje się, że zmienne lokalne powinny być deklarowane na początku funkcji (nie w wewnętrznych blokach, nie w for, itd.) - chyba, że stopień skomplikowania funkcji tego wymaga.

A dlaczego tak, jeśli można się spytać? Nie programuję w C zawodowo, ale dobre praktyki znane z innych języków mówią coś innego.

2

Jak dla mnie kanoniczne byłoby:

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    for (int i = 0; i < 10; i++)
        printf("%i\n", i);

    return EXIT_SUCCESS;
}

Innymi słowy, parametry funkcji main (choć niby bez nich kompiluje się bez ostrzeżeń); deklaracja int i wewnątrz pętli, bo nie jest używana poza nią, po co marnować miejsce; zaleca się stosować stałą EXIT_SUCCESS zamiast 0, czy to ze względu na nieużywanie stałych wpisanych na sztywno (czytelność), czy na to, że teoretycznie kod może być kompilowany na systemie, w którym jest inna konwencja. Natomiast ++i zamiast i++, które ktoś wspominał wyżej, to fanaberia, obie formy dają ten sam kod wynikowy (gcc 8.3.0). Może ewentualnie %d zamiast %i, jako forma powszechniejsza.

1
elwis napisał(a):

Innymi słowy, parametry funkcji main (choć niby bez nich kompiluje się bez ostrzeżeń); deklaracja int i wewnątrz pętli, bo nie jest używana poza nią, po co marnować miejsce;

Należałoby jeszcze oznaczyć argumenty argc i argv jako nieużywane, ale po co, skoro standard na okoliczność braku obsługi parametrów przewiduje int main(void) { /* ... */ }?

zaleca się stosować stałą EXIT_SUCCESS zamiast 0,

+1, chociaż trochę rozwlekle.

Ewentualnie EX_OK z <sysexists.h>

czy to ze względu na nieużywanie stałych wpisanych na sztywno (czytelność),

+1

czy na to, że teoretycznie kod może być kompilowany na systemie, w którym jest inna konwencja.

+1, chociaż tego bym się w praktyce nie spodziewał.

Natomiast ++i zamiast i++, które ktoś wspominał wyżej, to fanaberia, obie formy dają ten sam kod wynikowy (gcc 8.3.0).

+1

Może ewentualnie %d zamiast %i, jako forma powszechniejsza.

ymmv, ja się najczęściej spotykam z %i

ly000 napisał(a):
pifko napisał(a):

przyjmuje się, że zmienne lokalne powinny być deklarowane na początku funkcji (nie w wewnętrznych blokach, nie w for, itd.) - chyba, że stopień skomplikowania funkcji tego wymaga.

A dlaczego tak, jeśli można się spytać? Nie programuję w C zawodowo, ale dobre praktyki znane z innych języków mówią coś innego.

Tak się utarło, jednak często pojawiającym się argumentem jest że deklaracje zmiennych nie były porozrzucane po całej funkcji i od razu widać, na czym dana funkcja operuje, a jak zmiennych więcej jest niż kilka, to znaczy, że funkcja jest zbyt skomplikowana i należy ją podzielić - dla mnie jest to dobry argument.

Nie bez znaczenia pewnie jest fakt, że C to dość stary język, a w dawnych czasach interakcja z komputerami odbywała się między innymi za pomocą dalekopisów - raz deklarując wielokrotnie zmienną na początku można było zaoszczędzić trochę papieru i czasu, bo jedną z typowych prędkości transmisji było 300 bitów na sekundę, czyli po uwzględnieniu standardowego framingu jakieś 30 znaków na sekundę - a minimalną prędkością, którą obsługiwał Unix było chyba 50 bitów na sekundę, czyli 5 znaków na sekundę :)

Powrót karetki też swoje trwał, bo głowica musiała fizycznie wrócić na początek linii, więc trzeba było dodawać sztuczne opóźnienia, wysunięcie linii tak samo.

Później pojawiły się terminale CRT, one komunikowały się np. z szałową prędkością 1200 bitów na sekundę, a lokalne terminale 9600 lub nawet 19200 bitów na sekundę :)

Mogła być to więc taka prymitywna zasadą DRY, jeżeli np. w różnych blokach potrzebujemy int i lub czegoś w stylu struct buf *bp :)

0
Riddle napisał(a):

Dostałeś już odpowiedź, jakie. Jeśli chcesz z tym polemizować, to proponuję wrócić do początkowego tematu: https://4programmers.net/Forum/Kosz/364527-kod_sie_nie_kompiluje?p=1876412#id1876412, bo to tam jest całe sedno.

Szkopuł w tym, że nie dostałem - wygląda na to, że kolega @_13th_Dragon pomylił C z C++, stąd to rzekome złamanie dobrych praktyk - podsumowując:

  1. Koledze nie spodobało się void main(void) w C, bo odpowiednik w C++ jest bez void - tyle że w C brak jawnie podanych argumentów w deklaracji funkcji oznacza co innego niż w C++
  2. Kolega zobaczył for (i = 0; i < 10; i++), stwierdził błędnie, że musi zrobić kopię i ją zwrócić i wyskoczył ze swoimi wynurzeniami na temat kopii obiektów C++ (ale to jest C, gdzie konwencjonalnie pisze się i++ i nie ma żadnych konstruktorów kopiujących, ani konstruktorów w ogóle)
  3. Kolega nie daje sobie przetłumaczyć i z uporem maniaka widzi w C jakieś Bigint, streampos, iterator oraz wywołania konstruktorów i desktuktorów, i w dalszym ciągu twierdzi, że kod jest napisany w C++ nie przyjmując do wiadomości, że to jest C XD

Jakby ktoś nie programował zawodowo w C/C++, to służę przykładem - tak wygląda kod w C:

#include <stdio.h>

int main(void)
{
	int i;

	for (i = 0; i < 10; i++)
		printf("%i\n", i);
	return 0;
}

Oraz dla porównania podobny kod w C++:

#include <iostream>

int main()
{
	for (int i = 0; i < 10; ++i)
		std::cout << i << std::endl;
	return 0;
}

Znając życie, kolega @_13th_Dragon zaraz przyczepi się do jawnego return 0; w drugim przykładzie i zacznie nas edukować, że jest niepotrzebne, bo standard mówi wyraźnie, że można pominąć :P

0

w praktyce dostaniemy taki sam kod niezależnie wariantu i++ lub ++i.

Natomiast ++i zamiast i++, które ktoś wspominał wyżej, to fanaberia, obie formy dają ten sam kod wynikowy

tylko i wyłącznie w tym zapisie co został przedstawiony, poniżej wklejam listing
który wyjaśnia dlaczego jest bezpieczniej używać pre-inkrementacji ++i;

// (Debian 10.2.1-6) Code::Blocks 20.03 -std=c17
//gcc -Wall -g -std=c17  -c main.c -o main.o

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

int main()
{

    printf("Loop for");
    printf("\nPreinkrementacja\n");
    for(int i = 0; i<10; ++i){ printf("%d ", i); };
    printf("\nPostinkrementacja\n");
    for(int i = 0; i<10; i++){ printf("%d ", i); };

    // skrocony zapis
    printf("\n\nLoop for - very important\n");
    printf("Preinkrementacja\n");
    for(int i=log10(0.1); ++i<10; printf("%d ",i)){}
    printf("\nPostinkrementacja\n");
    for(int i=log10(0.1); i++<10; printf("%d ",i)){}    

    printf("\n\n\n");
    return EXIT_SUCCESS;
}


Kolega zobaczył for (i = 0; i < 10; i++), stwierdził błędnie, że musi zrobić kopię i ją zwrócić

używając post-inkrementacji i++; procesor tworzy wpierw kopię tej wartości
przed inkrementacją w celu zachowania nienaruszonej wartości

dla (i = 0; i++ < 10; )
Wartość i jest porównywana z wartościa 10, a następnie zwiększana,
po czym następuje wejście do pętli.

dla (i = 0; ++i < 10; )
Wartość i jest najpierw zwiększana, a następnie porównywana z wartością 10,
a następnie następuje wejście do pętli.

7

IMO preinkrementacja i postinkrementacja w C faktycznie jest prawie bez różnicy (poza różnicą semantyczną tam, gdzie ona zachodzi). Chociaż jestem też zdania, że lepiej pisać kod w stylu, którego nie trzeba zmieniać pisząc w C lub w C++.

Ale definicja zmiennych na początku funkcji to w najlepszym wypadku szkodliwy dla czytelności archaizm, a definicja zmiennych poza ich zakresem użycia nie ma żadnego uzasadnienia poza "używam kompilatora bazującego na gcc 1.5".

0
reich napisał(a):

używając post-inkrementacji i++; procesor tworzy wpierw kopię tej wartości
przed inkrementacją w celu zachowania nienaruszonej wartości

Bzdura.

dla (i = 0; i++ < 10; )
Wartość i jest porównywana z wartościa 10, a następnie zwiększana,
po czym następuje wejście do pętli.

U mnie jest napisane for (i = 0; i < 10; i++), nie (i = 0; i++ < 10; ) :)

dla (i = 0; ++i < 10; )
Wartość i jest najpierw zwiększana, a następnie porównywana z wartością 10,
a następnie następuje wejście do pętli.

U mnie jest napisane for (i = 0; i < 10; i++), nie (i = 0; ++i < 10; ) :)

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