LINUX.ORG.RU

Как дальше жить ? Или куда идет GCC(C)

 ,


1

5

Недавно занимался портированием рабочей системы на новый дистр (с целью тестирование и подготовкой будущего возможного апдейта продакшена). Столкнулся с тем что одна из используемых опенсорсных либ (либа уже года 3 как не развивается) не работает при сборке новым GCC с оптимизацией. Проковырялся целый день нашел в чем проблема - синтезировал простенький тест проверил в старых GCC + CLANG все ок, при сборке новым GCC (4.7 +) с -O2 не работает (c -O0 работает). Отписался GCC говорят и не должно работать так как GCC не потдерживает более язык С а работает только с ISO C - а с точки зрения стандарта ISO С код не рабочий. Вот интересно мнение общественности нужна ли потдержка компилятором такого кода (конкретно метода searchDict2):

#include <stdio.h>

struct kv_s
{
        int k;
        int v;
};
typedef struct kv_s kv_t;

struct dict_s
{
        kv_t kv1[1];
        kv_t kv2[1];
        kv_t kv3[1];
        kv_t kv4[1];
        kv_t kv5[1];
        kv_t kv6[1];
        kv_t kv7[1];
        kv_t kv8[1];
};
typedef struct dict_s dict_t;

void initDict(dict_t *dict)
{
        dict->kv1[0].k =  1;
        dict->kv1[0].v = -1;

        dict->kv2[0].k =  2;
        dict->kv2[0].v = -2;

        dict->kv3[0].k =  3;
        dict->kv3[0].v = -3;

        dict->kv4[0].k =  4;
        dict->kv4[0].v = -4;

        dict->kv5[0].k =  5;
        dict->kv5[0].v = -5;

        dict->kv6[0].k =  6;
        dict->kv6[0].v = -6;

        dict->kv7[0].k =  7;
        dict->kv7[0].v = -7;

        dict->kv8[0].k =  8;
        dict->kv8[0].v = -8;
}

int searchDict1(dict_t *dict, int key)
{
        int i;
        kv_t *kvs = dict->kv1;
        for(i=0; i<=6; i++)
        {
                if(key == kvs[i].k)
                {
                        return kvs[i].v;
                }
        }

        return 0;
}


int searchDict2(dict_t *dict, int key)
{
        int i;
        for(i=0; i<=6; i++)
        {
                if(key == dict->kv1[i].k)
                {
                        return dict->kv1[i].v;
                }
        }

        return 0;
}

int main(int argc, const char* argv[])
{
        dict_t dict;
        int res;

        initDict( &dict );

        res = searchDict1(&dict, 4);
        printf("Found1 %i\n", res);

        res = searchDict2(&dict, 4);
        printf("Found2 %i\n", res);

        return 0;
}

$ gcc test.c -O0 -o ./test-O0 && ./test-O0
Found1 -4
Found2 -4

$ gcc test.c -O2 -o ./test-O2 && ./test-O2
Found1 -4
Bus error

$ gcc test.c -O2 -fno-aggressive-loop-optimizations -o ./test-O2-nalo && ./test-O2-nalo
Found1 -4
Found2 -1
★★★★
Ответ на: комментарий от anonymous

CPU Intel Core i7 (Intel x86 - EMT64)

zaz ★★★★
() автор топика

Если стандарт говорит, что работать не должно, значит не должно. Все правильно сделали.

Deleted
()

Код бредовый. В первую очередь код должен поддерживаться человеком, а уже потом компилятором.

Что за библиотека?

ziemin ★★
()
Ответ на: комментарий от tailgunner

А что в коде не рабочего ? берем адресс на первое поле стректуры + 0 - получаем егоже + 1 - получаем kv2 + 2 - получаем kv3 и так далее Все поля одинаковой размерности и одинаково выравнены, коду который сломался уже лет 5 было, либа довольно распространенная работает на многох платформах а вот при сборке последними GCC с ней беда. Там конечно нахачено страшно - но это к теме не относится.

zaz ★★★★
() автор топика

Я в код пока не смотрел, но valgrind говорит, что всё плохо:

$ gcc -Wall -Wextra -O2 -ggdb -o test test.c 
test.c: In function ‘main’:
test.c:80:14: warning: unused parameter ‘argc’ [-Wunused-parameter]
 int main(int argc, const char* argv[])
              ^
test.c:80:32: warning: unused parameter ‘argv’ [-Wunused-parameter]
 int main(int argc, const char* argv[])
                                ^

$ valgrind ./test 
==5790== Memcheck, a memory error detector
==5790== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==5790== Using Valgrind-3.9.0 and LibVEX; rerun with -h for copyright info
==5790== Command: ./test
==5790== 
Found1 -4
==5790== Invalid read of size 4
==5790==    at 0x4004F3: main (test.c:58)
==5790==  Address 0x13fefff714 is not stack'd, malloc'd or (recently) free'd
==5790== 
==5790== 
==5790== Process terminating with default action of signal 11 (SIGSEGV)
==5790==  Access not within mapped region at address 0x13FEFFF714
==5790==    at 0x4004F3: main (test.c:58)
==5790==  If you believe this happened as a result of a stack
==5790==  overflow in your program's main thread (unlikely but
==5790==  possible), you can try to increase the size of the
==5790==  main thread stack using the --main-stacksize= flag.
==5790==  The main thread stack size used in this run was 8388608.
==5790== 
==5790== HEAP SUMMARY:
==5790==     in use at exit: 0 bytes in 0 blocks
==5790==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==5790== 
==5790== All heap blocks were freed -- no leaks are possible
==5790== 
==5790== For counts of detected and suppressed errors, rerun with: -v
==5790== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 2 from 2)
Segmentation fault (core dumped)

Deleted
()
Ответ на: комментарий от zaz

А что в коде не рабочего ?

AFAIK, выход за границу массива - это UB.

берем адресс

Я понимаю, как оно работало, и не спрашивал об этом.

tailgunner ★★★★★
()
Ответ на: комментарий от Deleted

ну валгринд проверяет не код а бинарник, понятное дело что в бинарнике все плохо - вопрос почему там все плохо ? Потомучто GCC делает чтото не то что от него хотят, либо потомучто от GCC хотят чегото плохого. Попробуй с -O0 и валгринд скажет что все ок.

zaz ★★★★
() автор топика
Ответ на: комментарий от zaz

А что в коде не рабочего ?

В стандарте написано UB, значит UB. Ты бы ещё на f(i++, i++) полагался. Ну а чо, все же знают, что задом наперёд в стек идут (это ирония).

mix_mix ★★★★★
()
Ответ на: комментарий от tailgunner

AFAIK, выход за границу массива - это UB.

Но ведь C был всегда языком в котором можно при желении выстрелить в себе в ногу ведь так ? А теперь при попытке прострелить себе ногу я попадаю в голову - что совсем не то что ожидается.

zaz ★★★★
() автор топика
Ответ на: комментарий от mix_mix

В принципе я согласен, но тогда хотелосб бы хоть какойто варнинг по этому поводу получить при компиляции. Судя по моей переписке с GCC варнинг им тоже мало интересен.

zaz ★★★★
() автор топика
Ответ на: комментарий от Deleted

Я в код пока не смотрел, но valgrind говорит, что всё плохо

и не смотри..там плохо..и gcc и clang бессильны пред человеком.

MKuznetsov ★★★★★
()

Ни по какому стандарту такой код работать не должен.

Deleted
()

Ассемблерный код смотрел, что там неправильного новый GCC выдаёт?

М.б. поможет указать директивами компилятора padding структур в 1 байт?

Harald ★★★★★
()
Ответ на: комментарий от zaz

теперь при попытке прострелить себе ногу я попадаю в голову

Кажется, у авторов компилятора другое представление о стрельбе по ногам, и они могут сослаться на стандарт :)

tailgunner ★★★★★
()
Ответ на: комментарий от zaz

Так не пойдёт, странного хочу не по стандартам, а в ассемблер нет желания :)

Harald ★★★★★
()

Отписался GCC говорят и не должно работать так как GCC не потдерживает более язык С а работает только с ISO C

И это хорошо, пам парам пам. Собирай в tcc ::)

Dron ★★★★★
()
Ответ на: комментарий от Harald

В ассемблер не лез, нету желания.

Падинги явно ни причем потомукак костыль с простым кастом на указатель работает отлично. Там есть 2 проблемы 1 у них указана в кнов исуесах: Переменная в форе используемая для доступа к массиву определенной размерности - при таком раскладе они выбрасывают проверку i<=6 при оптимизации в результате цыкл не завершается никогда i растет больше 8 и мы получаей сигфолт. В принципе бог с ней но очень хочется в этом случае варнинг от компилера, а то он тихо выкидывает проверку и при этом молчит даже при -Wall. Эта проблема как раз лечится ключиком -fno-aggressive-loop-optimizations

А вот вторая проблема уже более интересная там какойто странный код для доступа к этому злаполучному массиву - при поиске он находит нужный элемент (иф отрабатывает) но возвращает всегда первый.

zaz ★★★★
() автор топика
Ответ на: комментарий от tailgunner

Кажется, у авторов компилятора другое представление о стрельбе по ногам, и они могут сослаться на стандарт :)

В принципе они могут сослатся на что угодно - как хотят так и пишут. Я на все согласен :), мне бы варнингов при компиляции показали и я был бы доволен, хотябы о том что они выкинули проверку на i<=6 в форе, а еслиб они есще выдали варнинг что после оптимизации return dict->kv1[i].v; они выбрасили переменную i и заменили ее на константу 0 - то сщастью моему небыло бы предела :)

zaz ★★★★
() автор топика
Последнее исправление: zaz (всего исправлений: 2)

ги-сися смело шагает в будущее! вона, уже энтузиасты на андроиде вовсю используют графит для ускорения работы системы! о_О Стыдно не знать, товарищ!

darkenshvein ★★★★★
()
Ответ на: комментарий от zaz

В принципе они могут сослатся на что угодно

Могут, но сослались на стандарт :)

мне бы варнингов при компиляции показали и я был бы доволен

ЕМНИП, есть какой-то варнинг на выход за пределы массива.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

ЕМНИП, есть какой-то варнинг на выход за пределы массива.

Ну так покажите его, а то -Wextra -Wall бьют тревогу что argc и argv не используются а то что 1/4 кода тупо выкинули молчат.

zaz ★★★★
() автор топика

Вообще да, какая-то странная хрень происходит. searchDict2 выглядит вот так после -O2:

0000000000000070 <searchDict2>:
        return 0;
}*/


int searchDict2(dict_t *dict, int key)
{
  70:   8b 47 04                mov    0x4(%rdi),%eax
                        return dict->kv1[i].v;
                }
        }

        return 0;
}
  73:   c3                      retq   
WTF?

Если закомментировать вот этот кусок:

        /*res = searchDict1(&dict, 4);
        printf("Found1 %i\n", res);*/
то компилятор куда-то выкидывает main() вообще. Честно говоря, я вообще не понимаю как такое возможно.

Deleted
()
Ответ на: комментарий от zaz

Целый тред тупизны, просто весь.

А что в коде не рабочего ? берем адресс на первое поле стректуры + 0 - получаем егоже + 1 - получаем kv2 + 2 - получаем kv3 и так далее

Нет.

Все поля одинаковой размерности и одинаково выравнены

Нет. GCC идет к успеху, ты — значительно дальше.

t184256 ★★★★★
()
Ответ на: комментарий от zaz

ЕМНИП, есть какой-то варнинг на выход за пределы массива.

Ну так покажите его

-Warray-bounds

tailgunner ★★★★★
()
Ответ на: комментарий от Deleted

Я тоже посмотрел ассемблер похоже при -O2 searchDict2 превращается в 2 инструкции

movl 4(%rdi), %eax
ret
нет ни цикла ни ифов ассемблер удобней разбирать если запретить делать инлайн функции
int __attribute__ ((noinline)) searchDict2(dict_t *dict, int key)

zaz ★★★★
() автор топика
Ответ на: комментарий от LongLiveUbuntu

Версия gcc?

Воспроизводил проблему на gcc 4.7.3, 4.8.3 и 4.9.2 на 4.5.x проблема не воспроизводится, на clang который идет с последним XCode тоже, Microsoft Visual Studio - код работает как и ожидается.

zaz ★★★★
() автор топика
Последнее исправление: zaz (всего исправлений: 1)
Ответ на: комментарий от zaz

Пора бы знать что компилятор далеко не всегда может определить где нужен варнинг, а где нет. А ещё пора бы запомнить что если ты пишешь код не по стандарту, то всё - не будет никаких страховок в виде ворнингов, валгриндов и cppcheck'ов. Твой код действует по самому деструктивному сценарию, а ты оказываешься на улице с метлой. Больше всего умиляют попытки что-то возразить из разряда того как ты _думаешь_ этот код должен работать.

slovazap ★★★★★
()
Ответ на: комментарий от xaizek

Тут есть неплохое разъяснение.

Большое спасибо весьма интересный пост. В принципе опираясь на такое поведение ждать «ожидаемого» ответа не приходится, остается только надеятся что компилятор покажет варнинг - но к сожалению GCC этого не делает

zaz ★★★★
() автор топика
#include <stdio.h>

typedef struct kv_s {
	int k;
	int v;
} kv_t;

typedef union dict_u {
	struct {
		kv_t kv1[1];
		kv_t kv2[1];
		kv_t kv3[1];
		kv_t kv4[1];
		kv_t kv5[1];
		kv_t kv6[1];
		kv_t kv7[1];
		kv_t kv8[1];
	};
	kv_t kvs[8];
} dict_t;

void initDict(dict_t *dict) {
	dict->kv1[0].k =  1; dict->kv1[0].v = -1;
	dict->kv2[0].k =  2; dict->kv2[0].v = -2;
	dict->kv3[0].k =  3; dict->kv3[0].v = -3;
	dict->kv4[0].k =  4; dict->kv4[0].v = -4;
	dict->kv5[0].k =  5; dict->kv5[0].v = -5;
	dict->kv6[0].k =  6; dict->kv6[0].v = -6;
	dict->kv7[0].k =  7; dict->kv7[0].v = -7;
	dict->kv8[0].k =  8; dict->kv8[0].v = -8;
}

int searchDict1(dict_t *dict, int key) {
	int i;
	kv_t *kvs = dict->kvs;
	for (i = 0; i <= 6; i++) {
		if (key == kvs[i].k)
			return kvs[i].v;
	}
	return 0;
}

int searchDict2(dict_t *dict, int key) {
	int i;
	for (i = 0; i <= 6; i++) {
		if(key == dict->kvs[i].k)
			return dict->kvs[i].v;
	}
	return 0;
}

int main(int argc, const char* argv[]) {
	dict_t dict;
	int res;

	initDict(&dict);

	res = searchDict1(&dict, 4);
	printf("Found1 %i\n", res);

	res = searchDict2(&dict, 4);
	printf("Found2 %i\n", res);

	return 0;
}
i-rinat ★★★★★
()

И ещё вот. (w.c — код из заглавного сообщения)

$ gcc-4.9 -fsanitize=undefined -O2 w.c
$ ./a.out 
Found1 -4
w.c:71:39: runtime error: execution reached a __builtin_unreachable() call
i-rinat ★★★★★
()

Хочу присоединиться к малочисленной группе, которая считает, что gcc в данном случае генерирует ахинею вместо кода.

Ну ок, отбрось ты цикл, оставь только

if (key == dict->kv1[0].k)
    return dict->kv1[0].v;
Но нет, gcc самый умный и он оставляет один
return dict->kv1[0].v;
Более чем уверен, что если бы Линусу показали такое, он бы обгадил gcc и написал в рассылке: «Камрады, настало время двигаться в сторону clang, ибо gcc не тянет»

А вообще gcc скурвился, как это ни печально. У меня лично g++ + gperf + -O0 (или -fno-inline) = undefined reference при линковке.

kawaii_neko ★★★★
()
Ответ на: комментарий от kawaii_neko

Хотелось бы заметить, что gcc очевидно неправильно выбирает ветку if'а: нужно выбирать return 0, а не return dict->kv1[0].v. А Bus error за гранью моего понимания (может кто прояснит?)

kawaii_neko ★★★★
()
Ответ на: комментарий от kawaii_neko

И, кстати, 32-битный код вообще улет: бесконечный цикл без каких-либо ошибок от valgrind'a.

kawaii_neko ★★★★
()

Мне как-то показывали пример, когда "программист" пытался использовать переменные на стеке как массив. Что-то типа такого:

void foo() {
    int a1, a2, a3, a4;
    int *a = &a1;

    a[0] = 1;
    // ...
    a[3] = 3;

    // ну и тут какие-то действия
}

Так вот, что я скажу: поддержкой старых костылей пусть майкрософт занимается, это у них хорошо получается. Ибо писать надо по стандарту, а не "я у мамки хакир".

KennyMinigun ★★★★★
()
Последнее исправление: KennyMinigun (всего исправлений: 1)
Ответ на: комментарий от zaz

Но ведь C был всегда языком в котором можно при желении выстрелить в себе в ногу ведь так ? А теперь при попытке прострелить себе ногу я попадаю в голову - что совсем не то что ожидается.

ты точно ЭТОГО хотел?

ну говнокод жеж, с какого перепугу он должен компилироваться?! Рано или поздно это сломается.

emulek
()
Ответ на: комментарий от slovazap

Пора бы знать что компилятор далеко не всегда может определить где нужен варнинг, а где нет. А ещё пора бы запомнить что если ты пишешь код не по стандарту, то всё - не будет никаких страховок в виде ворнингов, валгриндов и cppcheck'ов. Твой код действует по самому деструктивному сценарию, а ты оказываешься на улице с метлой. Больше всего умиляют попытки что-то возразить из разряда того как ты _думаешь_ этот код должен работать.

++

emulek
()
Ответ на: комментарий от zaz

Просто не надо писать код, основанный на хаках. Надо читать стандарт.

peregrine ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.