Я хотел бы подготовить небольшой обучающий инструмент для SO, который должен помочь начинающим (и промежуточным) программистам распознавать и оспаривать свои необоснованные предположения в C, C ++ и их платформах.
Примеры:
- "целые числа"
- "у всех есть ASCII"
- "Я могу сохранить указатель на функцию в пустоте *"
Я полагал, что небольшая тестовая программа может быть запущена на различных платформах, которая запускает «правдоподобные» предположения, которые, исходя из нашего опыта работы с SO, обычно делаются многими неопытными / полуопытными разработчиками основного направления и фиксируют способы, которыми они ломаются на разных машинах.
Цель этого не в том, чтобы доказать, что что-то делать «безопасно» (что было бы невозможно, тесты доказывают что-либо, только если они сломаются), а вместо этого продемонстрировать даже самому непонятливому человеку, как самое незаметное выражение break на другом компьютере, если его поведение не определено или определено реализацией. ,
Для этого я хотел бы спросить вас:
- Как можно улучшить эту идею?
- Какие тесты подойдут и как они должны выглядеть?
- Не могли бы вы запустить тесты на доступных вам платформах и опубликовать результаты, чтобы мы получили базу данных платформ, их различий и почему это различие разрешено?
Вот текущая версия тестовой игрушки:
#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;
}
О, и я сделал эту вики сообщества с самого начала, потому что я решил, что люди хотят редактировать мою болтовню, когда они это читают.
ОБНОВЛЕНИЕ Спасибо за ваш вклад. Я добавил несколько примеров из ваших ответов и посмотрю, смогу ли я настроить github для этого, как предложил Грег.
ОБНОВЛЕНИЕ : Я создал для этого репозиторий на github, файл - gotcha.c:
Пожалуйста, ответьте здесь патчами или новыми идеями, чтобы их можно было здесь обсудить или уточнить. Затем я объединю их в gotcha.c.
источник
dlsym()
возвращает void *, но предназначен как для указателей данных, так и для указателей функций. Поэтому может быть не так уж плохо зависеть от этого.Ответы:
Порядок вычисления подвыражений, включая
+
,-
,=
,*
,/
), за исключением:&&
и||
),?:
) и,
)не указано
Например
источник
boost::spirit
)+
оператора не указан (разработчикам компилятора не нужно документировать поведение). Это не нарушает никаких правил точки последовательности как таковых.SDCC 29.7 / ucSim / Z80
printf вылетает. «О_О»
gcc 4.4@x86_64-suse-linux
gcc 4.4@x86_64-suse-linux (-O2)
clang 2.7@x86_64-suse-linux
open64 4.2.3@x86_64-suse-linux
intel 11.1@x86_64-suse-linux
Turbo C ++ / DOS / Малая память
Turbo C ++ / DOS / Средняя память
Turbo C ++ / DOS / Компактная память
cl65 @ Commodore PET (вице-эмулятор)
Я обновлю их позже:
Borland C ++ Builder 6.0 в Windows XP
Visual Studio Express 2010 C ++ CLR, 64-разрядная версия Windows 7
(должен быть скомпилирован как C ++, поскольку компилятор CLR не поддерживает чистый C)
MINGW64 (предварительная версия gcc-4.5.2)
- http://mingw-w64.sourceforge.net/
64-битная Windows использует модель LLP64: оба
int
иlong
определены как 32-битные, что означает, что ни одна из них не является достаточно длинной для указателя.avr-gcc 4.3.2 / ATmega168 (Arduino Diecimila)
Неудачные предположения:
Atmega168 имеет 16-битный ПК, но код и данные находятся в разных адресных пространствах. У больших Atmegas есть 22-битный ПК !.
gcc 4.2.1 в MacOSX 10.6, скомпилированный с -arch ppc
источник
sizeof(void*)>=sizeof(void(*)())
будет более актуальным, чем ==. Все мы заботимся о том , «мы можем хранить указатель на функцию в пустой указатель», так что предположение вам нужно проверить , является лиvoid*
это по крайней мере , столь же большой как указатель на функцию.sizeof(void*)>=sizeof(void(*)())
- см. Opengroup.org/onlinepubs/009695399/functions/dlsym.htmlДавным-давно я преподавал Си по учебнику, в котором
как образец вопроса. Это не удалось для ученика, потому что в этой реализации
sizeof
значения типаsize_t
, а неint
,int
были 16-битными иsize_t
были 32, и это было с прямым порядком байтов. (Платформа была Lightspeed C на Macintosh на базе 680x0. Я сказал, что это было давно.)источник
unsigned long long
там. Добавлено как Test 17.z
модификатор дляsize_t
целых чисел размера, а такжеlong long
не поддерживается на некоторых платформах. Таким образом, не существует безопасного переносимого способа форматирования или преобразования напечатанного размера объекта.Вы должны учитывать предположения
++
и--
предположения людей.Например, синтаксически допустимо, но дает разные результаты в зависимости от слишком многих вещей, чтобы их можно было рассуждать.
Любой оператор, который имеет
++
(или--
), а переменная встречается более одного раза, является проблемой.источник
Очень интересно!
Я могу подумать и о других вещах, которые было бы полезно проверить:
существуют ли указатели функций и указатели данных в одном адресном пространстве? (Бывает в машинах с гарвардской архитектурой, таких как малый режим DOS. Однако не знаю, как бы вы его протестировали.)
если вы возьмете указатель данных NULL и приведете его к соответствующему целочисленному типу, имеет ли он числовое значение 0? (На некоторых действительно древних машинах ломается - см. Http://c-faq.com/null/machexamp.html .) То же самое с указателем на функцию. Также они могут иметь разные значения.
приводит ли увеличение указателя к концу соответствующего объекта хранения и обратно к разумным результатам? (Я не знаю ни одной машины, на которой это действительно ломается, но я считаю, что спецификация C не позволяет вам даже думать об указателях, которые не указывают ни на (а) содержимое массива, или (б) на элемент сразу после массива или (c) NULL. См. http://c-faq.com/aryptr/non0based.html .)
дает ли сравнение двух указателей на разные объекты хранения с помощью <и> согласованные результаты? (Я могу представить это нарушение на экзотических машинах на основе сегментов; спецификация запрещает такие сравнения, поэтому компилятор будет иметь право сравнивать только часть смещения указателя, а не часть сегмента.)
Хм. Я попробую придумать еще кое-что.
Изменить: добавлены некоторые поясняющие ссылки на отличный FAQ по C.
источник
Я думаю, вам следует попытаться различить два очень разных класса «неправильных» предположений. Хорошая половина (сдвиг вправо и расширение знака, кодирование, совместимое с ASCII, линейная память, совместимость указателей данных и функций и т. Д.) Являются довольно разумными предположениями для большинства кодеров C, и могут даже быть включены как часть стандарта. если бы C разрабатывался сегодня, и если бы у нас не было устаревшего хлама IBM. Другая половина (вещи, связанные с псевдонимом памяти, поведение библиотечных функций при перекрытии входной и выходной памяти, 32-битные предположения, например, что указатели подходят
int
или которые вы можете использоватьmalloc
без прототипа это соглашение о вызовах идентично для вариативных и невариадных функций, ...) либо конфликтует с оптимизацией, которую хотят выполнить современные компиляторы, либо с миграцией на 64-битные машины или другой новой технологией.источник
malloc
без прототипа означает отказ от включения<stdlib.h>
, что приводитmalloc
к установке по умолчанию «int malloc(int)
нет-нет», если вы хотите поддерживать 64-разрядную версию.<stdlib.h>
пока вы включаете другой заголовок, который определяет,size_t
а затемmalloc
сами объявляете с правильным прототипом.Вот забавный вопрос: что не так с этой функцией?
[Ответ (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.)]
источник
Другой касается текстового режима в
fopen
. Большинство программистов предполагают, что текст и двоичный файл одинаковы (Unix) или что текстовый режим добавляет\r
символы (Windows). Но C был перенесен в системы, которые используют записи фиксированной ширины, чтоfputc('\n', file)
в текстовом файле означает добавление пробелов или чего-то еще, пока размер файла не станет кратным длине записи.И вот мои результаты:
gcc (Ubuntu 4.4.3-4ubuntu5) 4.4.3 на x86-64
источник
pow(2, n)
с битовыми операциями.Некоторые из них нелегко протестировать изнутри C, потому что программа может дать сбой в реализациях, где предположение не выполняется.
«С переменной, имеющей значение указателя, можно делать что угодно. Она должна содержать действительное значение указателя только в том случае, если вы разыменовываете ее».
То же самое с целочисленными типами и типами с плавающей запятой (кроме
unsigned char
), которым разрешено иметь представления прерывания.«Целочисленные вычисления повторяются. Итак, эта программа печатает большое отрицательное целое число».
(Только C89.) «Отвалиться с конца - нормально
main
».источник
gcc -ftrapv -O
выводомWe like to think that:
следуетAborted
main
без значения: программа верна, но возвращает неопределенный статус завершения (C89 §2.1.2.2). Во многих реализациях (таких как gcc и более старые компиляторы unix) вы получаете все, что было в определенном регистре на тот момент. Программа обычно работает до тех пор, пока она не будет использована в make-файле или другой среде, которая проверяет статус завершения.Что ж, классические допущения переносимости, которые еще не упоминаются,
источник
short
обратным порядком байтов, и стандарт разрешает такие странные вещи, как сохранение значения fedcab9876543210 (это 16 двоичных цифр) в виде двух байтов 0248ace и fdb97531.Ошибки дискретизации из-за представления с плавающей запятой. Например, если вы используете стандартную формулу для решения квадратных уравнений или конечные разности для аппроксимации производных, или стандартную формулу для расчета дисперсий, точность будет потеряна из-за вычисления разностей между похожими числами. Алгоритм Гаусса для решения линейных систем плох, потому что ошибки округления накапливаются, поэтому используется разложение QR или LU, разложение Холецкого, SVD и т. Д. Сложение чисел с плавающей запятой не ассоциативно. Есть денормальные, бесконечные и NaN значения. а + б - а ≠ б .
Строки: разница между символами, кодовыми точками и кодовыми единицами. Как Unicode реализован в различных операционных системах; Кодировки Unicode. Открытие файла с произвольным именем файла Unicode невозможно в C ++ переносимым способом.
Условия гонки, даже без потоковой передачи: если вы проверите, существует ли файл, результат может стать недействительным в любое время.
ERROR_SUCCESS
= 0источник
Включите проверку целочисленных размеров. Большинство людей полагают, что int больше, чем short, чем char. Однако все это может быть ложью:
sizeof(char) < sizeof(int); sizeof(short) < sizeof(int); sizeof(char) < sizeof(short)
Этот код может не работать (вылетает из-за невыровненного доступа)
источник
int *p = (int*)&buf[1];
на С ++, люди ожидают, что это тоже сработает.sizeof(char) < sizeof(int)
необходимо. Например, fgetc () возвращает значение символа как беззнаковый char, преобразованный в int, илиEOF
который является отрицательным значением.unsigned char
может не иметь битов заполнения, поэтому единственный способ сделать это - сделать int больше, чем char. Кроме того, (большинство версий) спецификации C требуется, чтобы любое значение из диапазона -32767..32767 могло храниться в int.Пару вещей о встроенных типах данных:
char
иsigned char
фактически являются двумя разными типами (в отличие отint
иsigned int
относятся к одному и тому же целочисленному типу со знаком).-3/5
можно было вернуть0
или-1
. Округление до нуля в случае, если один операнд был отрицательным, гарантируется только в C99 вверх и C ++ 0x вверх.int
имеет не менее 16 бит, along
имеет не менее 32 бит, along long
имеет не менее 64 бит. Afloat
может как минимум правильно представлять 6 самых значимых десятичных цифр. Adouble
может правильно представлять как минимум 10 наиболее значимых десятичных цифр.По общему признанию, на большинстве машин у нас будет два дополнения и числа с плавающей запятой IEEE 754.
источник
int mult(int a,int b) { return (long)a*b;}
[например, ifint
- 32 бита, но регистры иlong
- 64]. Без такого требования «естественное» поведение самой быстрой реализацииlong l=mult(1000000,1000000);
было бы установленоl
равным1000000000000
, даже если это «невозможное» значение дляint
.Как насчет этого:
Никакой указатель данных не может быть таким же, как действительный указатель на функцию.
Это ИСТИНА для всех плоских моделей, моделей MS-DOS TINY, LARGE и HUGE, ложно для модели MS-DOS SMALL и почти всегда неверно для моделей MEDIUM и COMPACT (в зависимости от адреса загрузки вам понадобится действительно старая DOS для сделай это правдой).
Я не могу написать тест на это
И что еще хуже: указатели, приведенные к ptrdiff_t, можно сравнивать. Это не верно для модели MS-DOS LARGE (единственное различие между LARGE и HUGE состоит в том, что HUGE добавляет код компилятора для нормализации указателей).
Я не могу написать тест, потому что среда, в которой эта бомба жестко взрывается, не выделяет буфер больше 64 КБ, поэтому код, демонстрирующий это, вылетит на других платформах.
Этот конкретный тест будет проходить в одной ныне несуществующей системе (обратите внимание, что это зависит от внутреннего устройства malloc):
источник
РЕДАКТИРОВАТЬ: Обновлено до последней версии программы
Solaris-SPARC
gcc 3.4.6 в 32 бит
gcc 3.4.6 в 64 бит
и с SUNStudio 11 32 бит
и с SUNStudio 11 64 бит
источник
Вы можете использовать text-mode (
fopen("filename", "r")
) для чтения любого текстового файла.Хотя теоретически это должно работать нормально, но если вы также используете его
ftell()
в своем коде, а текстовый файл имеет окончания строк в стиле UNIX, в некоторых версиях стандартной библиотеки Windowsftell()
часто будут возвращаться недопустимые значения. Решение состоит в том, чтобы использовать вместо этого двоичный режим (fopen("filename", "rb")
).источник
gcc 3.3.2 на AIX 5.3 (да, нам нужно обновить gcc)
источник
Предположение, которое некоторые могут делать в C ++, заключается в том, что a
struct
ограничено тем, что он может делать в C. Дело в том, что в C ++ astruct
подобен aclass
за исключением того, что по умолчанию у него все общедоступно.Структура C ++:
источник
Стандартные математические функции в разных системах не дают одинаковых результатов.
источник
Visual Studio Express 2010 на 32-разрядной версии x86.
источник
Через 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
).Обратите внимание, что у Codepad не было
stddef.h
. Я удалил тест 9 из-за того, что кодовая панель использовала предупреждения как ошибки. Я также переименовалcount
переменную, поскольку она по какой-то причине уже была определена.источник
Как насчет чрезмерного смещения вправо - это разрешено стандартом или стоит проверить?
Определяет ли стандарт C поведение следующей программы:
По крайней мере, в одном компиляторе, который я использую, этот код завершится ошибкой, если аргумент print_string не является "char const *". Допускает ли стандарт такое ограничение?
Некоторые системы позволяют создавать указатели на невыровненные int, а другие - нет. Возможно, стоит проверить.
источник
<<
и>>
). C99 имеет идентичный язык в §6.5.7-3.putch
(почему вы не использовали стандартputchar
?), Я не вижу неопределенного поведения в вашей программе. C89 §3.1.4 определяет, что «символьный строковый литерал имеет […] тип 'array of char'» (примечание: нетconst
) и что «если программа пытается изменить строковый литерал […], поведение не определено» , Что это за компилятор и как он транслирует эту программу?К вашему сведению, для тех, кому нужно перевести свои навыки C на Java, вот несколько ошибок.
В Java char является 16-битным и подписанным. байт 8-битный и подписанный.
long всегда 64-битный, ссылки могут быть 32-битными или 64-битными (если у вас более чем приложение с объемом более 32 ГБ). В 64-битных JVM обычно используются 32-битные ссылки.
Сдвиг маскируется так, что i << 64 == i == i << -64, i << 63 == i << -1
ByteOrder.nativeOrder () может иметь значение BIG_ENDIAN или LITTLE_ENDIAN
i = i++
никогда не меняетсяi
Размер коллекций и массивов всегда 32-битный, независимо от того, является ли JVM 32-битной или 64-битной.
char - 16-битный, short - 16-битный, int - 32-битный, long - 64-битный.
источник