Co należy do narzędzia edukacyjnego, aby zademonstrować nieuzasadnione założenia, które ludzie przyjmują w C / C ++?


121

Chciałbym przygotować małe narzędzie edukacyjne dla SO, które powinno pomóc początkującym (i średnio zaawansowanym) programistom rozpoznać i zakwestionować ich nieuzasadnione założenia w C, C ++ i ich platformach.

Przykłady:

  • „zawijanie liczb całkowitych”
  • „każdy ma ASCII”
  • „Mogę przechowywać wskaźnik funkcji w pustej przestrzeni *”

Doszedłem do wniosku, że mały program testowy można uruchomić na różnych platformach, zgodnie z "wiarygodnymi" założeniami, które z naszego doświadczenia w SO są zwykle poczynione przez wielu niedoświadczonych / częściowo doświadczonych programistów głównego nurtu i rejestrują sposoby ich łamania na różnych maszynach.

Celem tego nie jest udowodnienie, że można coś zrobić „bezpiecznie” (co byłoby niemożliwe, testy dowodzą tylko wszystkiego, jeśli się zepsują), ale zamiast tego zademonstrowanie nawet najbardziej niezrozumiałej osobie, jak najbardziej niepozorne wyrażenie przerwa na innym komputerze, jeśli ma niezdefiniowane lub zdefiniowane w implementacji zachowanie. .

Aby to osiągnąć, chciałbym Cię zapytać:

  • Jak można ulepszyć ten pomysł?
  • Które testy byłyby dobre i jak powinny wyglądać?
  • Czy przeprowadziłbyś testy na platformach, na których możesz dostać, i opublikował wyniki, abyśmy otrzymali bazę danych platform, czym się różnią i dlaczego ta różnica jest dozwolona?

Oto aktualna wersja zabawki testowej:

#include <stdio.h>
#include <limits.h>
#include <stdlib.h>
#include <stddef.h>
int count=0;
int total=0;
void expect(const char *info, const char *expr)
{
    printf("..%s\n   but '%s' is false.\n",info,expr);
    fflush(stdout);
    count++;
}
#define EXPECT(INFO,EXPR) if (total++,!(EXPR)) expect(INFO,#EXPR)

/* stack check..How can I do this better? */
ptrdiff_t check_grow(int k, int *p)
{
    if (p==0) p=&k;
    if (k==0) return &k-p;
    else return check_grow(k-1,p);
}
#define BITS_PER_INT (sizeof(int)*CHAR_BIT)

int bits_per_int=BITS_PER_INT;
int int_max=INT_MAX;
int int_min=INT_MIN;

/* for 21 - left to right */
int ltr_result=0;
unsigned ltr_fun(int k)
{
    ltr_result=ltr_result*10+k;
    return 1;
}

int main()
{
    printf("We like to think that:\n");
    /* characters */
    EXPECT("00 we have ASCII",('A'==65));
    EXPECT("01 A-Z is in a block",('Z'-'A')+1==26);
    EXPECT("02 big letters come before small letters",('A'<'a'));
    EXPECT("03 a char is 8 bits",CHAR_BIT==8);
    EXPECT("04 a char is signed",CHAR_MIN==SCHAR_MIN);

    /* integers */
    EXPECT("05 int has the size of pointers",sizeof(int)==sizeof(void*));
    /* not true for Windows-64 */
    EXPECT("05a long has at least the size of pointers",sizeof(long)>=sizeof(void*));

    EXPECT("06 integers are 2-complement and wrap around",(int_max+1)==(int_min));
    EXPECT("07 integers are 2-complement and *always* wrap around",(INT_MAX+1)==(INT_MIN));
    EXPECT("08 overshifting is okay",(1<<bits_per_int)==0);
    EXPECT("09 overshifting is *always* okay",(1<<BITS_PER_INT)==0);
    {
        int t;
        EXPECT("09a minus shifts backwards",(t=-1,(15<<t)==7));
    }
    /* pointers */
    /* Suggested by jalf */
    EXPECT("10 void* can store function pointers",sizeof(void*)>=sizeof(void(*)()));
    /* execution */
    EXPECT("11 Detecting how the stack grows is easy",check_grow(5,0)!=0);
    EXPECT("12 the stack grows downwards",check_grow(5,0)<0);

    {
        int t;
        /* suggested by jk */
        EXPECT("13 The smallest bits always come first",(t=0x1234,0x34==*(char*)&t));
    }
    {
        /* Suggested by S.Lott */
        int a[2]={0,0};
        int i=0;
        EXPECT("14 i++ is strictly left to right",(i=0,a[i++]=i,a[0]==1));
    }
    {
        struct {
            char c;
            int i;
        } char_int;
        EXPECT("15 structs are packed",sizeof(char_int)==(sizeof(char)+sizeof(int)));
    }
    {
        EXPECT("16 malloc()=NULL means out of memory",(malloc(0)!=NULL));
    }

    /* suggested by David Thornley */
    EXPECT("17 size_t is unsigned int",sizeof(size_t)==sizeof(unsigned int));
    /* this is true for C99, but not for C90. */
    EXPECT("18 a%b has the same sign as a",((-10%3)==-1) && ((10%-3)==1));

    /* suggested by nos */
    EXPECT("19-1 char<short",sizeof(char)<sizeof(short));
    EXPECT("19-2 short<int",sizeof(short)<sizeof(int));
    EXPECT("19-3 int<long",sizeof(int)<sizeof(long));
    EXPECT("20 ptrdiff_t and size_t have the same size",(sizeof(ptrdiff_t)==sizeof(size_t)));
#if 0
    {
        /* suggested by R. */
        /* this crashed on TC 3.0++, compact. */
        char buf[10];
        EXPECT("21 You can use snprintf to append a string",
               (snprintf(buf,10,"OK"),snprintf(buf,10,"%s!!",buf),strcmp(buf,"OK!!")==0));
    }
#endif

    EXPECT("21 Evaluation is left to right",
           (ltr_fun(1)*ltr_fun(2)*ltr_fun(3)*ltr_fun(4),ltr_result==1234));

    {
    #ifdef __STDC_IEC_559__
    int STDC_IEC_559_is_defined=1;
    #else 
    /* This either means, there is no FP support
     *or* the compiler is not C99 enough to define  __STDC_IEC_559__
     *or* the FP support is not IEEE compliant. */
    int STDC_IEC_559_is_defined=0;
    #endif
    EXPECT("22 floating point is always IEEE",STDC_IEC_559_is_defined);
    }

    printf("From what I can say with my puny test cases, you are %d%% mainstream\n",100-(100*count)/total);
    return 0;
}

Aha, i stworzyłem tę wspólnotową wiki od samego początku, ponieważ pomyślałem, że ludzie chcą edytować moją paplaninę, kiedy to czytają.

AKTUALIZACJA Dzięki za wkład. Dodałem kilka przypadków z twoich odpowiedzi i zobaczę, czy mogę założyć github, jak zasugerował Greg.

AKTUALIZACJA : W tym celu utworzyłem repozytorium github, plik to „gotcha.c”:

Prosimy o przesłanie tutaj poprawek lub nowych pomysłów, aby można je było omówić lub wyjaśnić tutaj. W takim razie połączę je w gotcha.c.


7
Rozważ średni model w DOS. Funkcje mogą być przechowywane w wielu segmentach, więc wskaźnik funkcji ma 32 bity. Ale twoje dane są przechowywane tylko w jednym segmencie, dlatego wskaźniki danych mają tylko 16 bitów. Ponieważ void * jest wskaźnikiem danych, ma 16 bitów szerokości, więc nie możesz zmieścić wskaźnika funkcji w jednym. Zobacz c-jump.com/CIS77/ASM/Directives/D77_0030_models.htm .
David Given

6
Być może mógłbyś wrzucić ten kod na github.com lub coś w tym rodzaju, a wtedy ludzie mogliby łatwo dodawać łaty.
Greg Hewgill,

1
Wiele rzeczy tutaj powinno pomóc: stackoverflow.com/questions/367633/ ...
Martin York

4
POSIX wymaga, aby wskaźniki funkcji miały taką samą reprezentację jak void * i mogły być konwertowane (z rzutowaniem) bez utraty informacji. Jednym z powodów jest to, że dlsym()zwraca void *, ale jest przeznaczony zarówno dla wskaźników danych, jak i funkcji. Dlatego poleganie na tym może nie być takie złe.
jilles

3
@tristopia: Punkt 15 jest tutaj, ponieważ wielu początkujących jest często zaskoczonych, gdy dowiaduje się, że dane nie są pakowane w sposób ciągły, ale wyrównane do pewnych granic. Są zdziwieni, gdy zmieniają kolejność elementów i uzyskują różne rozmiary obiektów. Pakowanie jest również domyślnym trybem wielu współczesnych mikrokontrolerów lub urządzeń wbudowanych. Moje wyjście AVR Atmega i TurboC / MSDOS też jest spakowane. MSDOS jest nadal używany w zastosowaniach przemysłowych.
Nordic Mainframe,

Odpowiedzi:


91

Kolejność oceny podwyrażeń, w tym

  • argumenty wywołania funkcji i
  • operandy operatorów (na przykład +, -, =, *, /), z wyjątkiem:
    • binarne operatory logiczne ( &&i ||),
    • potrójny operator warunkowy ( ?:) i
    • operator przecinka (, )

jest nieokreślony

Na przykład

  int Hello()
  {
       return printf("Hello"); /* printf() returns the number of 
                                  characters successfully printed by it
                               */
  }

  int World()
  {
       return printf("World !");
  }

  int main()
  {

      int a = Hello() + World(); //might print Hello World! or World! Hello
      /**             ^
                      | 
                Functions can be called in either order
      **/
      return 0;
  } 

1
Zawsze to wiedziałem o parametrach funkcji, ale nigdy nie myślałem o tym w kategoriach operatorów ... ... i jeśli kiedykolwiek zobaczę, jak piszesz taki kod w środowisku produkcyjnym, uderzę cię mokrym makaronem.
riwalk

3
@Billy: Ale tylko dla pierwotnych wersji operatorów.
Dennis Zickefoose

1
@Dennis: To prawda. (Dlatego jest to pozycja w Effective / MoreEffective C ++, która nigdy ich nie przeciąża (chyba że piszesz boost::spirit)
Billy ONeal

1
@Daniel: Nie jestem pewien, co próbujesz powiedzieć. Wygląda na to, że sugerujesz przeciążenie operatorów, ponieważ tylko użytkownicy Twojej klasy mogą się pomylić, a jeśli nie piszesz w prostym C ++, nie ma to znaczenia. Żadne z nich nie ma żadnego sensu.
Dennis Zickefoose

2
@ user420536: zachowanie jest po prostu nieokreślone, ale nie jest niezdefiniowane. Tak, przykład może wydrukować Hello World! lub świat! Witam, ale to nie jest określone, ponieważ kolejność oceny operandów +operatora jest nieokreślona (autorzy kompilatora nie muszą dokumentować zachowania). Nie narusza żadnej zasady dotyczącej punktów sekwencji jako takiej.
Prasoon Saurav

38

sdcc 29.7 / ucSim / Z80

We like to think that:
..09a minus shifts backwards
   but '(t=-1,(15<<t)==7)' is false.
..19-2 short<int
   but 'sizeof(short)<sizeof(int)' is false.
..22 floating point is always IEEE
   but 'STDC_IEC_559_is_defined' is false.
..25 pointer arithmetic works outside arrays
   but '(diff=&var.int2-&var.int1, &var.int1+diff==&var.int2)' is false.
From what I can say with my puny test cases, you are Stop at 0x0013f3: (106) Invalid instruction 0x00dd

printf ulega awarii. „O_O”


gcc 4.4@x86_64-suse-linux

We like to think that:
..05 int has the size of pointers
but 'sizeof(int)==sizeof(void*)' is false.
..08 overshifting is okay
but '(1<<bits_per_int)==0' is false.
..09a minus shifts backwards
but '(t=-1,(15<<t)==7)' is false.
..14 i++ is strictly left to right
but '(i=0,a[i++]=i,a[0]==1)' is false.
..15 structs are packed
but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
..17 size_t is unsigned int
but 'sizeof(size_t)==sizeof(unsigned int)' is false.
..26 sizeof() does not evaluate its arguments
but '(i=10,sizeof(char[((i=20),10)]),i==10)' is false.
From what I can say with my puny test cases, you are 79% mainstream

gcc 4.4@x86_64-suse-linux (-O2)

We like to think that:
..05 int has the size of pointers
but 'sizeof(int)==sizeof(void*)' is false.
..08 overshifting is okay
but '(1<<bits_per_int)==0' is false.
..14 i++ is strictly left to right
but '(i=0,a[i++]=i,a[0]==1)' is false.
..15 structs are packed
but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
..17 size_t is unsigned int
but 'sizeof(size_t)==sizeof(unsigned int)' is false.
..26 sizeof() does not evaluate its arguments
but '(i=10,sizeof(char[((i=20),10)]),i==10)' is false.
From what I can say with my puny test cases, you are 82% mainstream

clang 2.7@x86_64-suse-linux

We like to think that:
..05 int has the size of pointers
but 'sizeof(int)==sizeof(void*)' is false.
..08 overshifting is okay
but '(1<<bits_per_int)==0' is false.
..09a minus shifts backwards
but '(t=-1,(15<<t)==7)' is false.
..14 i++ is strictly left to right
but '(i=0,a[i++]=i,a[0]==1)' is false.
..15 structs are packed
but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
..17 size_t is unsigned int
but 'sizeof(size_t)==sizeof(unsigned int)' is false.
..21a Function Arguments are evaluated right to left
but '(gobble_args(0,ltr_fun(1),ltr_fun(2),ltr_fun(3),ltr_fun(4)),ltr_result==4321)' is false.
ltr_result is 1234 in this case
..25a pointer arithmetic works outside arrays
but '(diff=&p1-&p2, &p2+diff==&p1)' is false.
..26 sizeof() does not evaluate its arguments
but '(i=10,sizeof(char[((i=20),10)]),i==10)' is false.
From what I can say with my puny test cases, you are 72% mainstream

open64 4.2.3@x86_64-suse-linux

We like to think that:
..05 int has the size of pointers
but 'sizeof(int)==sizeof(void*)' is false.
..08 overshifting is okay
but '(1<<bits_per_int)==0' is false.
..09a minus shifts backwards
but '(t=-1,(15<<t)==7)' is false.
..15 structs are packed
but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
..17 size_t is unsigned int
but 'sizeof(size_t)==sizeof(unsigned int)' is false.
..21a Function Arguments are evaluated right to left
but '(gobble_args(0,ltr_fun(1),ltr_fun(2),ltr_fun(3),ltr_fun(4)),ltr_result==4321)' is false.
ltr_result is 1234 in this case
..25a pointer arithmetic works outside arrays
but '(diff=&p1-&p2, &p2+diff==&p1)' is false.
..26 sizeof() does not evaluate its arguments
but '(i=10,sizeof(char[((i=20),10)]),i==10)' is false.
From what I can say with my puny test cases, you are 75% mainstream

intel 11.1@x86_64-suse-linux

We like to think that:
..05 int has the size of pointers
but 'sizeof(int)==sizeof(void*)' is false.
..08 overshifting is okay
but '(1<<bits_per_int)==0' is false.
..09a minus shifts backwards
but '(t=-1,(15<<t)==7)' is false.
..14 i++ is strictly left to right
but '(i=0,a[i++]=i,a[0]==1)' is false.
..15 structs are packed
but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
..17 size_t is unsigned int
but 'sizeof(size_t)==sizeof(unsigned int)' is false.
..21a Function Arguments are evaluated right to left
but '(gobble_args(0,ltr_fun(1),ltr_fun(2),ltr_fun(3),ltr_fun(4)),ltr_result==4321)' is false.
ltr_result is 1234 in this case
..26 sizeof() does not evaluate its arguments
but '(i=10,sizeof(char[((i=20),10)]),i==10)' is false.
From what I can say with my puny test cases, you are 75% mainstream

Turbo C ++ / DOS / Mała pamięć

We like to think that:
..09a minus shifts backwards
but '(t=-1,(15<<t)==7)' is false.
..16 malloc()=NULL means out of memory
but '(malloc(0)!=NULL)' is false.
..19-2 short<int
but 'sizeof(short)<sizeof(int)' is false.
..22 floating point is always IEEE
but 'STDC_IEC_559_is_defined' is false.
..25 pointer arithmetic works outside arrays
but '(diff=&var.int2-&var.int1, &var.int1+diff==&var.int2)' is false.
..25a pointer arithmetic works outside arrays
but '(diff=&p1-&p2, &p2+diff==&p1)' is false.
From what I can say with my puny test cases, you are 81% mainstream

Turbo C ++ / DOS / Medium Memory

We like to think that:
..09a minus shifts backwards
but '(t=-1,(15<<t)==7)' is false.
..10 void* can store function pointers
but 'sizeof(void*)>=sizeof(void(*)())' is false.
..16 malloc()=NULL means out of memory
but '(malloc(0)!=NULL)' is false.
..19-2 short<int
but 'sizeof(short)<sizeof(int)' is false.
..22 floating point is always IEEE
but 'STDC_IEC_559_is_defined' is false.
..25 pointer arithmetic works outside arrays
but '(diff=&var.int2-&var.int1, &var.int1+diff==&var.int2)' is false.
..25a pointer arithmetic works outside arrays
but '(diff=&p1-&p2, &p2+diff==&p1)' is false.
From what I can say with my puny test cases, you are 78% mainstream

Turbo C ++ / DOS / pamięć kompaktowa

We like to think that:
..05 int has the size of pointers
but 'sizeof(int)==sizeof(void*)' is false.
..09a minus shifts backwards
but '(t=-1,(15<<t)==7)' is false.
..16 malloc()=NULL means out of memory
but '(malloc(0)!=NULL)' is false.
..19-2 short<int
but 'sizeof(short)<sizeof(int)' is false.
..20 ptrdiff_t and size_t have the same size
but '(sizeof(ptrdiff_t)==sizeof(size_t))' is false.
..22 floating point is always IEEE
but 'STDC_IEC_559_is_defined' is false.
..25 pointer arithmetic works outside arrays
but '(diff=&var.int2-&var.int1, &var.int1+diff==&var.int2)' is false.
..25a pointer arithmetic works outside arrays
but '(diff=&p1-&p2, &p2+diff==&p1)' is false.
From what I can say with my puny test cases, you are 75% mainstream

cl65 @ Commodore PET (wice emulator)

tekst alternatywny


Zaktualizuję te później:


Borland C ++ Builder 6.0 w systemie Windows XP

..04 a char is signed
   but 'CHAR_MIN==SCHAR_MIN' is false.
..08 overshifting is okay
   but '(1<<bits_per_int)==0' is false.
..09 overshifting is *always* okay
   but '(1<<BITS_PER_INT)==0' is false.
..09a minus shifts backwards
   but '(t=-1,(15<<t)==7)' is false.
..15 structs are packed
   but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
..16 malloc()=NULL means out of memory
   but '(malloc(0)!=NULL)' is false.
..19-3 int<long
   but 'sizeof(int)<sizeof(long)' is false.
..22 floating point is always IEEE
   but 'STDC_IEC_559_is_defined' is false.
From what I can say with my puny test cases, you are 71% mainstream

Visual Studio Express 2010 C ++ CLR, Windows 7 64-bitowy

(musi być skompilowany jako C ++, ponieważ kompilator CLR nie obsługuje czystego C)

We like to think that:
..08 overshifting is okay
   but '(1<<bits_per_int)==0' is false.
..09a minus shifts backwards
   but '(t=-1,(15<<t)==7)' is false.
..14 i++ is structly left to right
   but '(i=0,a[i++]=i,a[0]==1)' is false.
..15 structs are packed
   but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
..19-3 int<long
   but 'sizeof(int)<sizeof(long)' is false.
..22 floating point is always IEEE
   but 'STDC_IEC_559_is_defined' is false.
From what I can say with my puny test cases, you are 78% mainstream

MINGW64 (wersja wstępna gcc-4.5.2)

- http://mingw-w64.sourceforge.net/

We like to think that:
..05 int has the size of pointers
   but 'sizeof(int)==sizeof(void*)' is false.
..05a long has at least the size of pointers
   but 'sizeof(long)>=sizeof(void*)' is false.
..08 overshifting is okay
   but '(1<<bits_per_int)==0' is false.
..09a minus shifts backwards
   but '(t=-1,(15<<t)==7)' is false.
..14 i++ is structly left to right
   but '(i=0,a[i++]=i,a[0]==1)' is false.
..15 structs are packed
   but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
..17 size_t is unsigned int
   but 'sizeof(size_t)==sizeof(unsigned int)' is false.
..19-3 int<long
   but 'sizeof(int)<sizeof(long)' is false.
..22 floating point is always IEEE
   but 'STDC_IEC_559_is_defined' is false.
From what I can say with my puny test cases, you are 67% mainstream

64-bitowy system Windows używa modelu LLP64: oba inti longsą zdefiniowane jako 32-bitowe, co oznacza, że ​​żaden z nich nie jest wystarczająco długi na wskaźnik.


avr-gcc 4.3.2 / ATmega168 (Arduino Diecimila)

Błędne założenia to:

..14 i++ is structly left to right
..16 malloc()=NULL means out of memory
..19-2 short<int
..21 Evaluation is left to right
..22 floating point is always IEEE

Atmega168 ma 16-bitowy komputer PC, ale kod i dane znajdują się w oddzielnych przestrzeniach adresowych. Większe Atmegas mają 22-bitowy PC !.


gcc 4.2.1 na MacOSX 10.6, skompilowany z -arch ppc

We like to think that:
..09a minus shifts backwards
   but '(t=-1,(15<<t)==7)' is false.
..13 The smallest bits come always first
   but '(t=0x1234,0x34==*(char*)&t)' is false.
..14 i++ is structly left to right
   but '(i=0,a[i++]=i,a[0]==1)' is false.
..15 structs are packed
   but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
..19-3 int<long
   but 'sizeof(int)<sizeof(long)' is false.
..22 floating point is always IEEE
   but 'STDC_IEC_559_is_defined' is false.
From what I can say with my puny test cases, you are 78% mainstream


32
Zidentyfikowałeś inne założenie: możesz zmieścić 80 znaków w linii terminala.
Mike Seymour

3
sizeof(void*)>=sizeof(void(*)())byłoby bardziej odpowiednie niż ==. Jedyne, na czym nam zależy, to „czy możemy przechowywać wskaźnik funkcji w void pointer”, więc założeniem, które musisz przetestować, jest to, czy a void*jest co najmniej tak duże jak wskaźnik funkcji.
jalf

1
Jeśli twoje środowisko jest zgodne z POSIX, powinieneś być w porządku sizeof(void*)>=sizeof(void(*)())- zobacz opengroup.org/onlinepubs/009695399/functions/dlsym.html
Daniel Earwicker

26

Dawno temu uczyłem C z podręcznika, który miał

printf("sizeof(int)=%d\n", sizeof(int));

jako przykładowe pytanie. Nie udało się to uczniowi, ponieważ sizeofdaje wartości typu size_tnie int, intw tej implementacji było 16 bitów i size_t32, a było to big-endian. (Platformą była Lightspeed C na Macintoshach z 680x0. Powiedziałem, że to było dawno temu).


7
+1 za wskazanie jednego z najczęstszych i często pomijanych błędów tego rodzaju.
R .. GitHub PRZESTAŃ POMÓC W LODZIE

4
Dzieje się tak również w systemach 64-bitowych, gdzie size_t jest 64-bitowe, a wartości int są prawie zawsze krótsze. Win64 jest nadal dziwniejszy, ponieważ jest unsigned long longtam size_t . Dodano jako test 17.
Nordic Mainframe

Niestety środowisko uruchomieniowe C firmy Microsoft nie obsługuje zmodyfikatora dla size_tliczb całkowitych o rozmiarze i long longnie jest również obsługiwane na niektórych platformach. Nie ma więc bezpiecznego, przenośnego sposobu formatowania lub rzutowania rozmiaru wydruku obiektu.
Phil Miller,

15

Musisz uwzględnić przyjęte przez ludzi założenia ++i --.

a[i++]= i;

Na przykład jest legalny składniowo, ale daje różne wyniki w zależności od zbyt wielu rzeczy, aby je uzasadnić.

Każde stwierdzenie, które ma ++(lub --) i zmienną, która występuje więcej niż raz, stanowi problem.


I to też jest takie częste pytanie!
Matthieu M.,

8

Bardzo interesujące!

Inne rzeczy, które przychodzą mi do głowy, mogą być przydatne do sprawdzenia:

  • czy wskaźniki funkcji i wskaźniki danych istnieją w tej samej przestrzeni adresowej? (Przerwy w maszynach o architekturze Harvardu, takich jak mały tryb DOS. Nie wiem jednak, jak to przetestować).

  • jeśli weźmiesz wskaźnik danych NULL i rzucisz go na odpowiedni typ liczby całkowitej, czy ma on wartość liczbową 0? (Przerwy na niektórych naprawdę starych maszynach - patrz http://c-faq.com/null/machexamp.html .) Podobnie ze wskaźnikiem funkcji. Mogą też mieć różne wartości.

  • czy inkrementacja wskaźnika poza koniec odpowiadającego mu obiektu pamięci, a następnie z powrotem, daje sensowne wyniki? (Nie znam żadnych maszyn, na których to faktycznie się psuje, ale uważam, że specyfikacja C nie pozwala nawet myśleć o wskaźnikach, które nie wskazują ani (a) zawartości tablicy, ani (b) elementu bezpośrednio po tablicy lub (c) NULL. Zobacz http://c-faq.com/aryptr/non0based.html .)

  • czy porównanie dwóch wskaźników z różnymi obiektami pamięci za pomocą <i> daje spójne wyniki? (Mogę sobie wyobrazić to zerwanie na egzotycznych maszynach opartych na segmentach; specyfikacja zabrania takich porównań, więc kompilator byłby uprawniony do porównania tylko przesuniętej części wskaźnika, a nie części segmentu).

Hmm. Spróbuję pomyśleć o czymś więcej.

Edycja: Dodano kilka wyjaśniających linków do doskonałego C FAQ.


2
Nawiasem mówiąc, jakiś czas temu zrobiłem eksperymentalny projekt o nazwie Clue ( cluecc.sourceforge.net ), który pozwolił ci skompilować C do Lua, Javascript, Perl, LISP itp. Bezlitośnie wykorzystywał niezdefiniowane zachowanie w standardzie C, aby wskaźniki działały . Może być interesujące wypróbowanie na nim tego testu.
David Given

1
IIRC C pozwala na zwiększenie wskaźnika o 1 poza koniec obiektu, ale nie dalej. Zmniejszenie go do pozycji przed początkiem obiektu jest jednak niedozwolone.
R .. GitHub PRZESTAŃ POMÓC W LODZIE

@R. To samo w C ++. A dalsze zwiększanie może się zepsuć, jeśli zwiększanie wskaźnika spowoduje przepełnienie procesora, który nie traktuje wskaźników tylko jako liczb całkowitych.
jalf

5

Myślę, że powinieneś postarać się rozróżnić dwie bardzo różne klasy „niepoprawnych” założeń. Dobra połowa (przesunięcie w prawo i rozszerzenie znaku, kodowanie zgodne z ASCII, pamięć jest liniowa, wskaźniki danych i funkcji są zgodne itp.) To całkiem rozsądne założenia dla większości koderów C, a nawet mogą być włączone jako część standardu gdyby C projektowano dzisiaj i gdybyśmy nie mieli odziedziczonych po IBM śmieci. Druga połowa (rzeczy związane z aliasowaniem pamięci, zachowanie funkcji bibliotecznych w przypadku nakładania się pamięci wejściowej i wyjściowej, 32-bitowe założenia, takie jak te wskaźnikiint lub których możesz użyćmalloc bez prototypu, ta konwencja wywoływania jest identyczna dla funkcji wariadycznych i nie-wariadycznych, ...) albo koliduje z optymalizacjami, które chcą wykonać nowoczesne kompilatory, albo z migracją na maszyny 64-bitowe lub inną nową technologię.


to nie tylko „śmieci IBM” (chociaż zgadzam się, że rzeczy z IBM to śmieci). Wiele systemów wbudowanych ma obecnie podobne problemy.
rmeador

Aby wyjaśnić, używanie mallocbez prototypu oznacza nieuwzględnianie <stdlib.h>, co powoduje mallocdomyślnie int malloc(int), nie-nie, jeśli chcesz obsługiwać 64-bit.
Joey Adams,

Technicznie rzecz biorąc, możesz tego nie robić, <stdlib.h>o ile dołączysz inny nagłówek, który definiuje, size_ta następnie sam zadeklarujesz mallocz poprawnym prototypem.
R .. GitHub PRZESTAŃ POMÓC W LODZIE

5

Oto fajny: Co jest nie tak z tą funkcją?

float sum(unsigned int n, ...)
{
    float v = 0;
    va_list ap;
    va_start(ap, n);
    while (n--)
        v += va_arg(ap, float);
    va_end(ap);
    return v;
}

[Odpowiedź (rot13): Inevnqvp nethzragf borl gur byq X&E cebzbgvba ehyrf, juvpu zrnaf lbh pnaabg hfr 'sybng' (be 'pune' be 'fubeg') va in_net! Naq gur pbzcvyre vf erdhverq abg gb gerng guvf nf n pbzcvyr-gvzr reebe. (TPP qbrf rzvg n jneavat, gubhtu.)]


Och, to jest dobre. clang 2.7 zjada to i wydaje kompletny nonsens bez ostrzeżenia.
Nordic Mainframe,

va_arg rozwija się, jeśli jest to makro, a pętla while wykonuje tylko pierwszą instrukcję, być może wiele?
Maister

Nie (gdyby tak się stało, byłby to błąd w implementacji).
zwol

5
EXPECT("## pow() gives exact results for integer arguments", pow(2, 4) == 16);

Inny dotyczy trybu tekstowego w fopen. Większość programistów zakłada, że ​​tekst i plik binarny są takie same (Unix) lub że tryb tekstowy dodaje \rznaki (Windows). Ale C został przeniesiony do systemów, które używają rekordów o stałej szerokości, na którychfputc('\n', file) w pliku tekstowym oznacza się dodanie spacji lub czegoś podobnego, dopóki rozmiar pliku nie będzie wielokrotnością długości rekordu.

A oto moje wyniki:

gcc (Ubuntu 4.4.3-4ubuntu5) 4.4.3 na x86-64

We like to think that:
..05 int has the size of pointers
   but 'sizeof(int)==sizeof(void*)' is false.
..08 overshifting is okay
   but '(1<<bits_per_int)==0' is false.
..09a minus shifts backwards
   but '(t=-1,(15<<t)==7)' is false.
..14 i++ is strictly left to right
   but '(i=0,a[i++]=i,a[0]==1)' is false.
..15 structs are packed
   but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
..17 size_t is unsigned int
   but 'sizeof(size_t)==sizeof(unsigned int)' is false.
From what I can say with my puny test cases, you are 78% mainstream

Właściwie widziałem kod, który łączył się pow(2, n)z operacjami bitowymi.
dan04,

4

Niektórych z nich nie da się łatwo przetestować z poziomu C, ponieważ program prawdopodobnie ulegnie awarii w implementacjach, których założenie nie jest spełnione.


„Można cokolwiek zrobić ze zmienną o wartości wskaźnika. Musi ona zawierać prawidłową wartość wskaźnika tylko wtedy, gdy ją wyłuskujesz”.

void noop(void *p); /* A no-op function that the compiler doesn't know to optimize away */
int main () {
    char *p = malloc(1);
    free(p);
    noop(p); /* may crash in implementations that verify pointer accesses */
    noop(p - 42000); /* and if not the previous instruction, maybe this one */
}

To samo dotyczy typów całkowitych i zmiennoprzecinkowych (innych niż unsigned char), które mogą mieć reprezentacje pułapek.


„Obliczenia liczb całkowitych zawijają się. Więc ten program wypisuje dużą ujemną liczbę całkowitą”.

#include <stdio.h>
int main () {
    printf("%d\n", INT_MAX+1); /* may crash due to signed integer overflow */
    return 0;
}

(Tylko C89.) „Możesz spaść z końca main.”

#include <stdio.h>
int main () {
    puts("Hello.");
} /* The status code is 7 on many implementations. */

2
Jako konkretny przykład: po skompilowaniu z gcc -ftrapv -O, po wyjściu We like to think that:następujeAborted
caf

@caf: "Ta opcja generuje pułapki dla podpisanych przepełnień przy operacjach dodawania, odejmowania i mnożenia." Miło wiedzieć, dzięki.
Gilles 'SO- przestań być zły'

1
Ten ostatni jest również w porządku w C ++ (98, 03 i 0x) i niejawnie zwraca 0.
jalf

Co jest paskudne, ponieważ przed ANSI C zezwalało na to, a C99 również.
Joshua

@Joshua: AFAIK nie ma różnicy między C89 przed ANSI i C89 po powrocie mainz bez wartości: program jest poprawny, ale zwraca niezdefiniowany stan zakończenia (C89 §2.1.2.2). W przypadku wielu implementacji (takich jak gcc i starsze kompilatory unixowe) otrzymujesz wszystko, co było w pewnym rejestrze w tym momencie. Program zazwyczaj działa, dopóki nie zostanie użyty w pliku makefile lub innym środowisku, które sprawdza stan zakończenia.
SO- Gilles 'SO- przestań być zły'

4

Cóż, klasyczne założenia dotyczące przenośności, o których jeszcze nie wspomniano, są

  • założenia dotyczące wielkości typów całkowitych
  • endianness

4
„Endianness”, w tym „There is a endianness”: istnieją maszyny ze średniej półki, a standard pozwala na dziwne rzeczy, takie jak przechowywanie shortwartości fedcab9876543210 (czyli 16 cyfr binarnych) jako dwóch bajtów 0248ace i fdb97531.
Gilles 'SO- przestań być zły'

tak endianess na pewno obejmuje mieszany / średni endian, a także duży i mały. Jeśli pójdziesz do niestandardowego sprzętu, możesz mieć każdy endianess, który lubisz w każdym autobusie.
jk.

Middle endian jest znany jako endian PDP. Gilles opisuje jednak coś jeszcze dziwniejszego, co spowodowałoby ból głowy przy implementacji TCP / IP.
Joshua

@Gilles: middle-endian ... Bardzo się cieszę, że nie rozwijam się na tym. (ale jestem pewien, że teraz poproszono mnie o zrobienie projektu sieci w średnim końcu) ...
Paul Nathan,

ARM FPE wykorzystywało dublety ze średniego końca, w których były one przechowywane jako para <high ​​quad> <low quad>, ale kolejność bitów w każdym quadzie była odwrotna. (Na szczęście ARM VFP już tego nie robi.)
David Given

4
  • Błędy dyskretyzacji spowodowane reprezentacją zmiennoprzecinkową. Na przykład, jeśli użyjesz standardowej formuły do ​​rozwiązywania równań kwadratowych lub skończonych różnic do przybliżonych pochodnych lub standardowej formuły do ​​obliczania wariancji, precyzja zostanie utracona z powodu obliczania różnic między podobnymi liczbami. Algorytm Gaußa do rozwiązywania układów liniowych jest zły, ponieważ gromadzą się błędy zaokrągleń, dlatego stosuje się dekompozycję QR lub LU, dekompozycję Choleskiego, SVD itp. Dodawanie liczb zmiennoprzecinkowych nie jest asocjacyjne. Istnieją wartości denormalne, nieskończone i NaN. a + b - ab .

  • Ciągi znaków: różnica między znakami, punktami kodowymi i jednostkami kodu. Sposób implementacji Unicode w różnych systemach operacyjnych; Kodowanie Unicode. Otwarcie pliku z dowolną nazwą pliku Unicode nie jest możliwe w C ++ w sposób przenośny.

  • Warunki wyścigu, nawet bez wątków: jeśli przetestujesz, czy plik istnieje, wynik może stać się nieważny w dowolnym momencie.

  • ERROR_SUCCESS = 0


4

Uwzględnij sprawdzenie rozmiarów całkowitych. Większość ludzi zakłada, że ​​int jest większe niż short jest większe niż char. Jednak wszystko to może być fałszywe:sizeof(char) < sizeof(int); sizeof(short) < sizeof(int); sizeof(char) < sizeof(short)

Ten kod może się nie powieść (awarie przy dostępie bez wyrównania)

unsigned char buf[64];

int i = 234;
int *p = &buf[1];
*p = i;
i = *p;

czy ten kod zawiedzie w C ++? IIRC, niedozwolone jest rzutowanie wskaźników między niepowiązanymi typami, Z WYJĄTKIEM znaku *, który może być rzutowany na dowolny typ (czy jest odwrotnie?).
rmeador

1
Możesz po prostu zrobić int *p = (int*)&buf[1];w c ++, ludzie oczekują, że to też zadziała.
nr

@nos, tak, to może się nie powieść, ale niepowodzenie jest awarią, więc jego program nie może przetestować tego. :(
Joshua,

1
sizeof(char) < sizeof(int)jest wymagane. Na przykład fgetc () zwraca wartość znaku jako unsigned char przekonwertowany na int lub EOFbędący wartością ujemną. unsigned charmoże nie mieć bitów wypełniających, więc jedynym sposobem na to jest uczynienie wartości int większym niż char. Ponadto (większość wersji) specyfikacji C wymaga, aby każda wartość z zakresu -32767..32767 mogła być przechowywana w int.
jilles

@illes nadal istnieją procesory DSP z 32-bitowymi znakami i 32-bitowymi intami.
nr

3

Kilka rzeczy o wbudowanych typach danych:

  • chari signed charsą w rzeczywistości dwoma różnymi typami (w przeciwieństwie inti signed intktóre odnoszą się do tego samego typu liczb całkowitych ze znakiem).
  • liczby całkowite ze znakiem nie są wymagane, aby używać uzupełnień do dwóch. Uzupełnienie jedności i znak + wielkość są również poprawnymi reprezentacjami liczb ujemnych. To sprawia, że ​​operacje bitowe obejmujące liczby ujemne są zdefiniowane implementacyjnie .
  • Jeśli przypiszesz liczbę całkowitą spoza zakresu do zmiennej całkowitej ze znakiem, zachowanie jest zdefiniowane w implementacji .
  • W C90 -3/5może powrócić 0lub -1. Zaokrąglanie w kierunku zera w przypadku, gdy jeden operand był ujemny, jest gwarantowane tylko w C99 w górę i C ++ 0x w górę.
  • Nie ma gwarancji dokładnego rozmiaru dla typów wbudowanych. Standard obejmuje tylko minimalne wymagania, takie jak intma co najmniej 16 bitów, a longma co najmniej 32 bity, a long longma co najmniej 64 bity. A floatmoże poprawnie reprezentować przynajmniej 6 najbardziej znaczących cyfr dziesiętnych. A doublemoże poprawnie reprezentować przynajmniej 10 najbardziej znaczących cyfr dziesiętnych.
  • IEEE 754 nie jest obowiązkowy do przedstawiania liczb zmiennoprzecinkowych.

Trzeba przyznać, że na większości maszyn będziemy mieć dwa uzupełnienia i zmiennoprzecinkowe IEEE 754.


Zastanawiam się, jaką wartość ma przypisanie spoza zakresu liczb całkowitych zdefiniowane w ramach implementacji, a nie niezdefiniowane zachowanie? Na niektórych platformach taki wymóg zmusiłby kompilator do wygenerowania dodatkowego kodu dla int mult(int a,int b) { return (long)a*b;}[np. Jeśli intma 32 bity, ale rejestruje i longjest 64]. Bez takiego wymagania „naturalne” zachowanie najszybszej implementacji long l=mult(1000000,1000000);byłoby lrówne 1000000000000, mimo że jest to wartość „niemożliwa” dla pliku int.
supercat,

3

A co z tym:

Żaden wskaźnik danych nie może być taki sam jak prawidłowy wskaźnik funkcji.

Jest to PRAWDA dla wszystkich płaskich modeli, modeli MS-DOS TINY, LARGE i HUGE, fałszywa dla modelu MS-DOS SMALL i prawie zawsze fałszywa dla modeli MEDIUM i COMPACT (w zależności od adresu ładowania, będziesz potrzebować naprawdę starego DOS uczynić to prawdziwym).

Nie mogę na to napisać testu

I gorzej: można porównać wskaźniki rzutowane na ptrdiff_t. Nie dotyczy to modelu MS-DOS LARGE (jedyna różnica między LARGE i HUGE polega na tym, że HUGE dodaje kod kompilatora w celu znormalizowania wskaźników).

Nie mogę napisać testu, ponieważ środowisko, w którym te bomby są trudne, nie przydzieli bufora większego niż 64K, więc kod, który to demonstruje, zawiesiłby się na innych platformach.

Ten konkretny test przeszedłby na jeden nieistniejący już system (zauważ, że zależy to od elementów wewnętrznych malloc):

  char *ptr1 = malloc(16);
  char *ptr2 = malloc(16);
  if ((ptrdiff_t)ptr2 - 0x20000 == (ptrdiff_t)ptr1)
      printf("We like to think that unrelated pointers are equality comparable when cast to the appropriate integer, but they're not.");

3

EDYCJA: Zaktualizowano do ostatniej wersji programu

Solaris-SPARC

gcc 3.4.6 w wersji 32-bitowej

We like to think that:
..08 overshifting is okay
   but '(1<<bits_per_int)==0' is false.
..09 overshifting is *always* okay
   but '(1<<BITS_PER_INT)==0' is false.
..09a minus shifts backwards
   but '(t=-1,(15<<t)==7)' is false.
..13 The smallest bits always come first
   but '(t=0x1234,0x34==*(char*)&t)' is false.
..14 i++ is strictly left to right
   but '(i=0,a[i++]=i,a[0]==1)' is false.
..15 structs are packed
   but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
..19-3 int<long
   but 'sizeof(int)<sizeof(long)' is false.
..22 floating point is always IEEE
   but 'STDC_IEC_559_is_defined' is false.
From what I can say with my puny test cases, you are 72% mainstream

gcc 3.4.6 w wersji 64-bitowej

We like to think that:
..05 int has the size of pointers
   but 'sizeof(int)==sizeof(void*)' is false.
..08 overshifting is okay
   but '(1<<bits_per_int)==0' is false.
..09 overshifting is *always* okay
   but '(1<<BITS_PER_INT)==0' is false.
..09a minus shifts backwards
   but '(t=-1,(15<<t)==7)' is false.
..13 The smallest bits always come first
   but '(t=0x1234,0x34==*(char*)&t)' is false.
..14 i++ is strictly left to right
   but '(i=0,a[i++]=i,a[0]==1)' is false.
..15 structs are packed
   but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
..17 size_t is unsigned int
   but 'sizeof(size_t)==sizeof(unsigned int)' is false.
..22 floating point is always IEEE
   but 'STDC_IEC_559_is_defined' is false.
From what I can say with my puny test cases, you are 68% mainstream

i 32-bitowym SUNStudio 11

We like to think that:
..08 overshifting is okay
   but '(1<<bits_per_int)==0' is false.
..09a minus shifts backwards
   but '(t=-1,(15<<t)==7)' is false.
..13 The smallest bits always come first
   but '(t=0x1234,0x34==*(char*)&t)' is false.
..14 i++ is strictly left to right
   but '(i=0,a[i++]=i,a[0]==1)' is false.
..15 structs are packed
   but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
..19-3 int<long
   but 'sizeof(int)<sizeof(long)' is false.
From what I can say with my puny test cases, you are 79% mainstream

oraz z SUNStudio 11 w wersji 64-bitowej

We like to think that:
..05 int has the size of pointers
   but 'sizeof(int)==sizeof(void*)' is false.
..08 overshifting is okay
   but '(1<<bits_per_int)==0' is false.
..09a minus shifts backwards
   but '(t=-1,(15<<t)==7)' is false.
..13 The smallest bits always come first
   but '(t=0x1234,0x34==*(char*)&t)' is false.
..14 i++ is strictly left to right
   but '(i=0,a[i++]=i,a[0]==1)' is false.
..15 structs are packed
   but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
..17 size_t is unsigned int
   but 'sizeof(size_t)==sizeof(unsigned int)' is false.
From what I can say with my puny test cases, you are 75% mainstream

2

Możesz użyć trybu tekstowego ( fopen("filename", "r")) do czytania dowolnego rodzaju pliku tekstowego.

Chociaż teoretycznie powinno to działać dobrze, jeśli używasz również ftell()w swoim kodzie, a plik tekstowy ma zakończenia linii w stylu UNIX, w niektórych wersjach biblioteki standardowej Windows ftell()często zwraca nieprawidłowe wartości. Rozwiązaniem jest użycie trybu binarnego ( fopen("filename", "rb")).


1

gcc 3.3.2 w systemie AIX 5.3 (tak, musimy zaktualizować gcc)

We like to think that:
..04 a char is signed
   but 'CHAR_MIN==SCHAR_MIN' is false.
..09a minus shifts backwards
   but '(t=-1,(15<<t)==7)' is false.
..13 The smallest bits come always first
   but '(t=0x1234,0x34==*(char*)&t)' is false.
..14 i++ is structly left to right
   but '(i=0,a[i++]=i,a[0]==1)' is false.
..15 structs are packed
   but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
..16 malloc()=NULL means out of memory
   but '(malloc(0)!=NULL)' is false.
..19-3 int<long
   but 'sizeof(int)<sizeof(long)' is false.
..22 floating point is always IEEE
   but 'STDC_IEC_559_is_defined' is false.
From what I can say with my puny test cases, you are 71% mainstream

1

Założenie, które niektórzy mogą zrobić w C ++, jest takie, że a structjest ograniczone do tego, co może zrobić w C. Faktem jest, że w C ++ a structjest podobne do a, classz tym wyjątkiem, że domyślnie ma wszystko publiczne.

Struktura C ++:

struct Foo
{
  int number1_;  //this is public by default


//this is valid in C++:    
private: 
  void Testing1();
  int number2_;

protected:
  void Testing2();
};

1

Standardowe funkcje matematyczne w różnych systemach nie dają identycznych wyników.


1

Visual Studio Express 2010 na 32-bitowej architekturze x86.

Z:\sandbox>cl testtoy.c
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.30319.01 for 80x86
Copyright (C) Microsoft Corporation.  All rights reserved.

testtoy.c
testtoy.c(54) : warning C4293: '<<' : shift count negative or too big, undefined
 behavior
Microsoft (R) Incremental Linker Version 10.00.30319.01
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:testtoy.exe
testtoy.obj

Z:\sandbox>testtoy.exe
We like to think that:
..08 overshifting is okay
   but '(1<<bits_per_int)==0' is false.
..09a minus shifts backwards
   but '(t=-1,(15<<t)==7)' is false.
..14 i++ is structly left to right
   but '(i=0,a[i++]=i,a[0]==1)' is false.
..15 structs are packed
   but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
..19-3 int<long
   but 'sizeof(int)<sizeof(long)' is false.
..22 floating point is always IEEE
   but 'STDC_IEC_559_is_defined' is false.
From what I can say with my puny test cases, you are 78% mainstream

1

Poprzez Codepad.org ( C++: g++ 4.1.2 flags: -O -std=c++98 -pedantic-errors -Wfatal-errors -Werror -Wall -Wextra -Wno-missing-field-initializers -Wwrite-strings -Wno-deprecated -Wno-unused -Wno-non-virtual-dtor -Wno-variadic-macros -fmessage-length=0 -ftemplate-depth-128 -fno-merge-constants -fno-nonansi-builtins -fno-gnu-keywords -fno-elide-constructors -fstrict-aliasing -fstack-protector-all -Winvalid-pch).

Zwróć uwagę, że Codepad nie miał stddef.h. Usunąłem test 9, ponieważ kodowanie używało ostrzeżeń jako błędów. Zmieniłem też nazwę countzmiennej, ponieważ z jakiegoś powodu została już zdefiniowana.

We like to think that:
..08 overshifting is okay
   but '(1<<bits_per_int)==0' is false.
..14 i++ is structly left to right
   but '(i=0,a[i++]=i,a[0]==1)' is false.
..15 structs are packed
   but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
..19-3 int<long
   but 'sizeof(int)<sizeof(long)' is false.
From what I can say with my puny test cases, you are 84% mainstream

1

A co z przesunięciem w prawo o nadmierne kwoty - czy jest to dozwolone przez normę, czy też warte sprawdzenia?

Czy Standard C określa zachowanie następującego programu:

void print_string (char * st)
{
  char ch;
  while ((ch = * st ++)! = 0)
    putch (ch); / * Załóżmy, że to jest zdefiniowane * /
}
int main (nieważne)
{
  print_string ("Hello");
  return 0;
}

Na przynajmniej jednym kompilatorze, którego używam, ten kod nie powiedzie się, chyba że argument print_string to "char const *". Czy norma dopuszcza takie ograniczenie?

Niektóre systemy pozwalają na tworzenie wskaźników do niewyrównanych int, a inne nie. Może warto przetestować.


C89 §3.3.7: „Jeśli wartość prawego operandu jest ujemna lub jest większa lub równa szerokości w bitach promowanego lewego operandu, zachowanie jest nieokreślone.” (dotyczy obu <<i >>). C99 ma identyczny język w §6.5.7-3.
SO- Gilles 'SO- przestań być zły'

Poza tym putch(dlaczego nie użyłeś standardu putchar?), Nie widzę w Twoim programie żadnego niezdefiniowanego zachowania. C89 §3.1.4 określa, że ​​„literał ciągu znaków ma typ […] 'tablica znaków'” (uwaga: nie const) oraz że „jeśli program próbuje zmodyfikować literał ciągu znaków […], zachowanie jest niezdefiniowane” . Co to za kompilator i jak tłumaczy ten program?
Gilles 'SO- przestań być zły'

2
W C ++ stałe znakowe nie są char [], są stałymi char []. Jednak ... kiedyś istniała specyficzna dziura w systemie typów, umożliwiająca użycie stałej łańcuchowej w kontekście, w którym oczekiwano znaku * i nie otrzymywano błędu typu. Doprowadziło to do sytuacji, w których print_string ("foo") działał, ale print_string ("foo" +0) nie. Było to głęboko mylące, szczególnie w środowiskach, w których pliki C są domyślnie kompilowane przy użyciu kompilatora C ++. Dziura została usunięta w nowych kompilatorach, ale wciąż jest wiele starych. AFAIK C99 nadal definiuje stałe łańcuchowe jako char [].
David Given

1
W kompilatorach HiTech dla serii kontrolerów Microchip PIC wskaźnik bez kwalifikatora pamięci może wskazywać tylko na pamięć RAM. Wskaźnik z kwalifikacją const może wskazywać na pamięć RAM lub ROM. Wskaźniki niekwalifikowane do const są wyłuskiwane bezpośrednio w kodzie; Wskaźniki z kwalifikacją const są wyłuskiwane za pomocą procedury bibliotecznej. W zależności od konkretnego typu PIC, wskaźniki niekwalifikowane do stałej mają 1 lub 2 bajty; Kwalifikowane do stałych to 2 lub 3. Ponieważ ROM jest dużo więcej niż RAM, posiadanie stałych w ROM jest ogólnie dobrą rzeczą.
supercat

@David Given: Zwróć też uwagę na mój poprzedni komentarz. Preferuję kompilatory, które używają kwalifikatorów innych niż „const” do określenia klasy pamięci masowej; kompilator HiTech ma pewne dość irytujące dziwactwa związane z przydzielaniem klas pamięci (np. elementy danych, których „rozmiar komponentu” to bajt, lub elementy danych, które mają ponad 256 bajtów, są umieszczane w „dużym” segmencie. Inne elementy danych są umieszczane w sekcji „ bss ”dla modułu, który jest zdefiniowany; wszystkie elementy„ bss ”w module muszą mieścić się w 256 bajtach. Tablice, które mają nieco mniej niż 256 bajtów, mogą być naprawdę uciążliwe.
supercat

0

Do Twojej wiadomości, dla tych, którzy muszą przetłumaczyć swoje umiejętności C na Javę, oto kilka pułapek.

EXPECT("03 a char is 8 bits",CHAR_BIT==8);
EXPECT("04 a char is signed",CHAR_MIN==SCHAR_MIN);

W Javie znak char jest 16-bitowy i podpisany. bajt jest 8-bitowy i podpisany.

/* not true for Windows-64 */
EXPECT("05a long has at least the size of pointers",sizeof(long)>=sizeof(void*));

long jest zawsze 64-bitowe, odwołania mogą być 32-bitowe lub 64-bitowe (jeśli masz więcej niż aplikację o pojemności większej niż 32 GB) 64-bitowe maszyny JVM zwykle używają odwołań 32-bitowych.

EXPECT("08 overshifting is okay",(1<<bits_per_int)==0);
EXPECT("09 overshifting is *always* okay",(1<<BITS_PER_INT)==0);

Przesunięcie jest zamaskowane, więc i << 64 == i == i << -64, i << 63 == i << -1

EXPECT("13 The smallest bits always come first",(t=0x1234,0x34==*(char*)&t));

ByteOrder.nativeOrder () może mieć wartość BIG_ENDIAN lub LITTLE_ENDIAN

EXPECT("14 i++ is strictly left to right",(i=0,a[i++]=i,a[0]==1));

i = i++ nigdy się nie zmienia i

/* suggested by David Thornley */
EXPECT("17 size_t is unsigned int",sizeof(size_t)==sizeof(unsigned int));

Rozmiar kolekcji i tablic jest zawsze 32-bitowy, niezależnie od tego, czy maszyna JVM jest 32-bitowa, czy 64-bitowa.

EXPECT("19-1 char<short",sizeof(char)<sizeof(short));
EXPECT("19-2 short<int",sizeof(short)<sizeof(int));
EXPECT("19-3 int<long",sizeof(int)<sizeof(long));

char to 16-bit, short to 16-bit, int to 32-bit, a long to 64-bit.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.