Есть ли компилятор C, который не может скомпилировать это?
Я некоторое время болтал в своем профилировщике, пытаясь понять, как ускорить общий анализатор журналов, который был узким местом вокруг синтаксического анализа даты, и я пробовал различные алгоритмы для ускорения работы.
То, что я пробовал, что было самым быстрым для меня, было также самым читаемым, но потенциально нестандартным C.
Это хорошо отразилось на GCC, icc, и мой действительно старый и придирчивый компилятор SGI. Поскольку это довольно читаемая оптимизация, где она не делает то, что я хочу?
static int parseMonth(const char *input) {
int rv=-1;
int inputInt=0;
int i=0;
for(i=0; i<4 && input[i]; i++) {
inputInt = (inputInt << 8) | input[i];
}
switch(inputInt) {
case 'Jan/': rv=0; break;
case 'Feb/': rv=1; break;
case 'Mar/': rv=2; break;
case 'Apr/': rv=3; break;
case 'May/': rv=4; break;
case 'Jun/': rv=5; break;
case 'Jul/': rv=6; break;
case 'Aug/': rv=7; break;
case 'Sep/': rv=8; break;
case 'Oct/': rv=9; break;
case 'Nov/': rv=10; break;
case 'Dec/': rv=11; break;
}
return rv;
}
Ответы
Ответ 1
Solaris 10 - компилятор SPARC - SUN.
Тестовый код:
#include <stdio.h>
static int parseMonth(const char *input) {
int rv=-1;
int inputInt=0;
int i=0;
for(i=0; i<4 && input[i]; i++) {
inputInt = (inputInt << 8) | input[i];
}
switch(inputInt) {
case 'Jan/': rv=0; break;
case 'Feb/': rv=1; break;
case 'Mar/': rv=2; break;
case 'Apr/': rv=3; break;
case 'May/': rv=4; break;
case 'Jun/': rv=5; break;
case 'Jul/': rv=6; break;
case 'Aug/': rv=7; break;
case 'Sep/': rv=8; break;
case 'Oct/': rv=9; break;
case 'Nov/': rv=10; break;
case 'Dec/': rv=11; break;
}
return rv;
}
static const struct
{
char *data;
int result;
} test_case[] =
{
{ "Jan/", 0 },
{ "Feb/", 1 },
{ "Mar/", 2 },
{ "Apr/", 3 },
{ "May/", 4 },
{ "Jun/", 5 },
{ "Jul/", 6 },
{ "Aug/", 7 },
{ "Sep/", 8 },
{ "Oct/", 9 },
{ "Nov/", 10 },
{ "Dec/", 11 },
{ "aJ/n", -1 },
};
#define DIM(x) (sizeof(x)/sizeof(*(x)))
int main(void)
{
size_t i;
int result;
for (i = 0; i < DIM(test_case); i++)
{
result = parseMonth(test_case[i].data);
if (result != test_case[i].result)
printf("!! FAIL !! %s (got %d, wanted %d)\n",
test_case[i].data, result, test_case[i].result);
}
return(0);
}
Результаты (GCC 3.4.2 и Солнце):
$ gcc -O xx.c -o xx
xx.c:14:14: warning: multi-character character constant
xx.c:15:14: warning: multi-character character constant
xx.c:16:14: warning: multi-character character constant
xx.c:17:14: warning: multi-character character constant
xx.c:18:14: warning: multi-character character constant
xx.c:19:14: warning: multi-character character constant
xx.c:20:14: warning: multi-character character constant
xx.c:21:14: warning: multi-character character constant
xx.c:22:14: warning: multi-character character constant
xx.c:23:14: warning: multi-character character constant
xx.c:24:14: warning: multi-character character constant
xx.c:25:14: warning: multi-character character constant
$ ./xx
$ cc -o xx xx.c
$ ./xx
!! FAIL !! Jan/ (got -1, wanted 0)
!! FAIL !! Feb/ (got -1, wanted 1)
!! FAIL !! Mar/ (got -1, wanted 2)
!! FAIL !! Apr/ (got -1, wanted 3)
!! FAIL !! May/ (got -1, wanted 4)
!! FAIL !! Jun/ (got -1, wanted 5)
!! FAIL !! Jul/ (got -1, wanted 6)
!! FAIL !! Aug/ (got -1, wanted 7)
!! FAIL !! Sep/ (got -1, wanted 8)
!! FAIL !! Oct/ (got -1, wanted 9)
!! FAIL !! Nov/ (got -1, wanted 10)
!! FAIL !! Dec/ (got -1, wanted 11)
$
Обратите внимание, что последний тестовый пример все еще прошел - то есть он сгенерировал -1.
Здесь пересмотренная - более подробная версия parseMonth(), которая работает одинаково как у компилятора GCC и Sun C:
#include <stdio.h>
/* MONTH_CODE("Jan/") does not reduce to an integer constant */
#define MONTH_CODE(x) ((((((x[0]<<8)|x[1])<<8)|x[2])<<8)|x[3])
#define MONTH_JAN (((((('J'<<8)|'a')<<8)|'n')<<8)|'/')
#define MONTH_FEB (((((('F'<<8)|'e')<<8)|'b')<<8)|'/')
#define MONTH_MAR (((((('M'<<8)|'a')<<8)|'r')<<8)|'/')
#define MONTH_APR (((((('A'<<8)|'p')<<8)|'r')<<8)|'/')
#define MONTH_MAY (((((('M'<<8)|'a')<<8)|'y')<<8)|'/')
#define MONTH_JUN (((((('J'<<8)|'u')<<8)|'n')<<8)|'/')
#define MONTH_JUL (((((('J'<<8)|'u')<<8)|'l')<<8)|'/')
#define MONTH_AUG (((((('A'<<8)|'u')<<8)|'g')<<8)|'/')
#define MONTH_SEP (((((('S'<<8)|'e')<<8)|'p')<<8)|'/')
#define MONTH_OCT (((((('O'<<8)|'c')<<8)|'t')<<8)|'/')
#define MONTH_NOV (((((('N'<<8)|'o')<<8)|'v')<<8)|'/')
#define MONTH_DEC (((((('D'<<8)|'e')<<8)|'c')<<8)|'/')
static int parseMonth(const char *input) {
int rv=-1;
int inputInt=0;
int i=0;
for(i=0; i<4 && input[i]; i++) {
inputInt = (inputInt << 8) | input[i];
}
switch(inputInt) {
case MONTH_JAN: rv=0; break;
case MONTH_FEB: rv=1; break;
case MONTH_MAR: rv=2; break;
case MONTH_APR: rv=3; break;
case MONTH_MAY: rv=4; break;
case MONTH_JUN: rv=5; break;
case MONTH_JUL: rv=6; break;
case MONTH_AUG: rv=7; break;
case MONTH_SEP: rv=8; break;
case MONTH_OCT: rv=9; break;
case MONTH_NOV: rv=10; break;
case MONTH_DEC: rv=11; break;
}
return rv;
}
static const struct
{
char *data;
int result;
} test_case[] =
{
{ "Jan/", 0 },
{ "Feb/", 1 },
{ "Mar/", 2 },
{ "Apr/", 3 },
{ "May/", 4 },
{ "Jun/", 5 },
{ "Jul/", 6 },
{ "Aug/", 7 },
{ "Sep/", 8 },
{ "Oct/", 9 },
{ "Nov/", 10 },
{ "Dec/", 11 },
{ "aJ/n", -1 },
{ "/naJ", -1 },
};
#define DIM(x) (sizeof(x)/sizeof(*(x)))
int main(void)
{
size_t i;
int result;
for (i = 0; i < DIM(test_case); i++)
{
result = parseMonth(test_case[i].data);
if (result != test_case[i].result)
printf("!! FAIL !! %s (got %d, wanted %d)\n",
test_case[i].data, result, test_case[i].result);
}
return(0);
}
Я хотел использовать MONTH_CODE(), но компиляторы не сотрудничали.
Ответ 2
if ( !input[0] || !input[1] || !input[2] || input[3] != '/' )
return -1;
switch ( input[0] )
{
case 'F': return 1; // Feb
case 'S': return 8; // Sep
case 'O': return 9; // Oct
case 'N': return 10; // Nov
case 'D': return 11; // Dec;
case 'A': return input[1] == 'p' ? 3 : 7; // Apr, Aug
case 'M': return input[2] == 'r' ? 2 : 4; // Mar, May
default: return input[1] == 'a' ? 0 : (input[2] == 'n' ? 5 : 6); // Jan, Jun, Jul
}
Немного менее читабельна и не очень достоверна, но, возможно, даже быстрее, нет?
Ответ 3
Вы просто вычисляете хэш этих четырех символов. Почему бы не предопределить некоторые целочисленные константы, которые вычисляют хэш таким же образом и используют их? Такая же читаемость, и вы не зависите от каких-либо специфических особенностей реализации компилятора.
uint32_t MONTH_JAN = 'J' << 24 + 'a' << 16 + 'n' << 8 + '/';
uint32_t MONTH_FEB = 'F' << 24 + 'e' << 16 + 'b' << 8 + '/';
...
static uint32_t parseMonth(const char *input) {
uint32_t rv=-1;
uint32_t inputInt=0;
int i=0;
for(i=0; i<4 && input[i]; i++) {
inputInt = (inputInt << 8) | (input[i] & 0x7f); // clear top bit
}
switch(inputInt) {
case MONTH_JAN: rv=0; break;
case MONTH_FEB: rv=1; break;
...
}
return rv;
}
Ответ 4
Я знаю только то, что говорит об этом C-C (C99):
Значение целочисленного символа константа, содержащая более одного символ (например, "ab" ) или содержащий символ или escape-последовательность, которая не отображается в однобайтовый исполняемый символ, реализация-де определены. Если целое число символьная константа содержит один символа или escape-последовательности, его значение - это результат, когда объект с типом char, значение которого равно одиночный символ или побег последовательность преобразуется в тип int.
(6.4.4.4/10, взятый из черновика)
Итак, эта реализация определена. Это означает, что он не гарантирует, что он работает одинаково повсюду, но поведение должно быть документировано реализацией. Например, если int
имеет ширину всего 16 бит в конкретной реализации, то 'Jan/'
больше не может быть представлен, как вы его намереваетесь (char
должно быть не менее 8 бит, тогда как символьный литерал всегда имеет тип int
).
Ответ 5
char *months = "Jan/Feb/Mar/Apr/May/Jun/Jul/Aug/Sep/Oct/Nov/Dec/";
char *p = strnstr(months, input, 4);
return p ? (p - months) / 4 : -1;
Ответ 6
Компилятор National Instrument CVI 8.5 для Windows скомпрометирует ваш исходный код с несколькими предупреждениями:
Warning: Excess characters in multibyte character literal ignored.
и ошибки вида:
Duplicate case label '77'.
Он преуспевает в коде Джонатана.
Ответ 7
Существует не менее 3 вещей, которые не позволяют переносить эту программу:
- Многосимвольные константы определяются реализацией, поэтому разные компиляторы могут обрабатывать их по-разному.
- Байт может быть более 8 бит, есть много аппаратного обеспечения, где наименьшая адресуемая единица памяти равна 16 или даже 32 битам, часто вы можете найти это в DSP, например. Если в байте больше 8 бит, тогда будет
char
, так как char
по определению длится один байт; ваша программа не будет функционировать должным образом в таких системах.
- Наконец, существует множество машин, где
int
- это только 16 бит (это наименьший размер, разрешенный для int), включая встроенные устройства и унаследованные машины. Ваша программа также не работает на этих машинах.
Ответ 8
Я получаю предупреждения, но никаких ошибок (gcc). Кажется, компилировать и работать нормально. Может не работать для систем с большими номерами, однако!
Я бы не предложил этот метод. Возможно, вы можете xor вместо or-shift, чтобы создать один байт. Затем используйте оператор case в байте (или, быстрее, используйте LUT из первых N бит).
Ответ 9
Comau компилятор
Comeau C/C++ 4.3.10.1 (Oct 6 2008 11:28:09) for ONLINE_EVALUATION_BETA2
Copyright 1988-2008 Comeau Computing. All rights reserved.
MODE:strict errors C99
"ComeauTest.c", line 11: warning: multicharacter character literal (potential
portability problem)
case 'Jan/': rv=0; break;
^
"ComeauTest.c", line 12: warning: multicharacter character literal (potential
portability problem)
case 'Feb/': rv=1; break;
^
"ComeauTest.c", line 13: warning: multicharacter character literal (potential
portability problem)
case 'Mar/': rv=2; break;
^
"ComeauTest.c", line 14: warning: multicharacter character literal (potential
portability problem)
case 'Apr/': rv=3; break;
^
"ComeauTest.c", line 15: warning: multicharacter character literal (potential
portability problem)
case 'May/': rv=4; break;
^
"ComeauTest.c", line 16: warning: multicharacter character literal (potential
portability problem)
case 'Jun/': rv=5; break;
^
"ComeauTest.c", line 17: warning: multicharacter character literal (potential
portability problem)
case 'Jul/': rv=6; break;
^
"ComeauTest.c", line 18: warning: multicharacter character literal (potential
portability problem)
case 'Aug/': rv=7; break;
^
"ComeauTest.c", line 19: warning: multicharacter character literal (potential
portability problem)
case 'Sep/': rv=8; break;
^
"ComeauTest.c", line 20: warning: multicharacter character literal (potential
portability problem)
case 'Oct/': rv=9; break;
^
"ComeauTest.c", line 21: warning: multicharacter character literal (potential
portability problem)
case 'Nov/': rv=10; break;
^
"ComeauTest.c", line 22: warning: multicharacter character literal (potential
portability problem)
case 'Dec/': rv=11; break;
^
"ComeauTest.c", line 1: warning: function "parseMonth" was declared but never
referenced
static int parseMonth(const char *input) {
^
Ответ 10
Тот факт, что константа из четырех символов эквивалентна определенному 32-битовому целому числу, представляет собой нестандартную функцию, часто встречающуюся в компиляторах для компьютеров MS Windows и Mac (и PalmOS, AFAICR).
В этих системах четырехзначная строка обычно используется как тег для идентификации фрагментов файлов данных или как идентификатор типа приложения/данных (например, APPL).
Это удобство для разработчиков, чтобы они могли хранить такую строку в различных структурах данных, не беспокоясь о завершении нулевого байта, указателях и т.д.
Ответ 11
Проблемы с размером машинного слова в стороне, ваш компилятор может продвигать ввод [i] в отрицательное целое число, которое будет просто устанавливать верхние биты inputInt с помощью или операции, поэтому я предлагаю вам быть явным о подписи переменных char.
Но поскольку в США никто не заботится о 8-м бите, это, вероятно, не проблема для вас.
Ответ 12
Я бы с удовольствием посмотрел профилирование, в котором показано, что это ваше самое значительное узкое место, но в любом случае, если вы собираетесь что-то сделать, используйте объединение вместо 50 инструкций, связанных и перемещающихся. Вот небольшая примерная программа, я оставлю ее вам, чтобы поместить ее в вашу программу.
/* union -- demonstrate union for characters */
#include <stdio.h>
union c4_i {
char c4[5];
int i ;
} ;
union c4_i ex;
int main (){
ex.c4[0] = 'a';
ex.c4[1] = 'b';
ex.c4[2] = 'c';
ex.c4[3] = 'd';
ex.c4[4] = '\0';
printf("%s 0x%08x\n", ex.c4, ex.i );
return 0;
}
Здесь пример вывода:
bash $ ./union
abcd 0x64636261
bash $
Ответ 13
Как уже упоминалось другими, этот код выдает кучу предупреждений и, вероятно, не является безопасным для пользователя.
Был ли ваш оригинал синтаксического анализа даты написанным вручную? Вы пробовали strptime (3)?