gcc. maybe-uninitialized, как узнать ветку, которую gcc считает плохой

Phoenix

Есть функция вида
 

struct T1
{
int value;
};
struct T1
{
double value;
};
struct T3
{
long long int value;
};
int func(T1 &t1, T2 &t2, T3 &t3, int param)
{
int ret = 0;
if(param > 100)
{
ret |= 0x1;
t1.value = 10;
}
if(param > 10)
{
ret |= 0x2;
t2.value = 10;
}
if(param > 1000000)
{
ret |= 0x4;
t3.value = 10;
}
return ret;
}

она возвращает битовую маску того, какой из параметров установлен.
вызывается так
 
  
T1 t1;
T2 t2;
T3 t3;
int a=0;
double b=0;
long long c=0;
for(int i=0;i<100;i++)
{
int ret = func(t1,t2,t3,i );
if(ret & 0x1)
{
a = t1.value;
}

if(ret & 0x2)
{
b = t2.value;
}

if(ret & 0x4)
{
c = t3.value;
}
// do smth with a,b,c
}

пишет
 
 
In file included from ...
from ...
...:552:23: warning: 't1.T1::value' may be used uninitialized in this function [-Wmaybe-uninitialized]
a = t1.value;
^
In file included from ...:14:0:
...:828:24: note: ''t1.T1::value' was declared here
T1 t1;


Повторить этот ворнинг на маленьком примере не получилось. Тот код, что выше компилируется без ворнингов.
Почему так (через ссылки и битовую маску) коряво, — ничего лучше я не придумал. Нужно было минимизировать всё, что только можно. funс находится в другом конце приложения.
Указатели - лишнее разыменовывания (хотя может это я туплю и gcc -O3 их бы убрал ? )
Но мне стало интересно, как узнать, что компилятор считает uninitialized может быть, т.е. что это за такая ветка.
Глазами я посмотрел, ничего не нашёл, т.е. большая вероятность, что компилятор ошибаетя.
На примере попроще - ничего не получилось. Есть подозрение, что компилятор func делает inline и ничего плохо там не надоходит.

ppplva

Извини, но это какой-то поток сознания. Запости хотя бы функцию, на которую он ругается.
Учти, что он может и ошибаться (_maybe_ uninitialized).

Phoenix

Я вот и хочу узнать, что он там maybe, чтобы понять, прав он или нет. Одно место, где он ругается, я проверил полностью, там он ошибается.
пример поправил, но у меня повторить не получилось на более простом, поэтому я там имена файлов затёр

erotic


int func(T1 &t1, T2 &t2, T3 &t3, int param)
int ret = func(t1,t2,t3);

В объявлении 4 параметра, в вызове - 3. В этом случае обычно другие ошибки валятся.

Phoenix

опечаточка, да
int ret = func(t1,t2,t3,i );

margadon

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

Phoenix

нет конструкторов.
да, после конструктора по-умолчанию они и будут чем угодно. Просто я к ним обращаюсь только, если они уже не "что угодно".

margadon

незаметно
судя по твоему коду ты сразу после инициализации фигачишь на них функцию... по-моему нужно привести полный код

Phoenix

там они и устанавливаются. Возвращаемое значение через ссылку же.

margadon

кроме t3

Phoenix

аа, так флаг для t3 и не ставится в функции. Добавил кусок, чтобы unused variable не было

erotic

По-моему загвоздка где-то в том, что ты компилируешь один код, а в форум постишь другой.

Phoenix

да, я ж писал выше, что не смог придумать простй пример, который даёт такую же ошибку. Вроде все основные места из боевого кода перенёс - а ошибки нет.
Вот и хочу узнать "трейс компилятора" или что-то такое. При отладке же прогеры комилятора что-то такое используют.

bleyman

Почему так (через ссылки и битовую маску) коряво, — ничего лучше я не придумал. Нужно было минимизировать всё, что только можно. funс находится в другом конце приложения. Указатели - лишнее разыменовывания (хотя может это я туплю и gcc -O3 их бы убрал ? )
Что за бред вообще.
Почему битовая маска а не числа 0, 1, 2, ... и свитч?
Почему возвращаешь структуры разного типа если в результате нужно из них инициализировать одно и то же как бы?
Что значит, "указатели — лишние разыменования", как ты думаешь у тебя структуры в функцию передаются, через астрал?
У тебя реально инициализация — затык по производительности? Ты вообще прогу профилировал перед тем как безумные оптимизации воротить, хотя бы чтобы проверить что они действительно делают код быстрее, если не для того, чтобы узнать, нужно ли париться вообще?
Почему гцц ругается на строчку t1.value = 10; где ты её инициализируешь, а не используешь?

evolet

потелепатю
у тебя if'ы внутри функции скоррелированы с if'ами после нее.
В простом случае видимо компилятор обнаруживает корреляцию и соответственно может доказать что нет использования неинициализированных переменных.
В сложном видимо корреляцию не довазывает и видит путь исполнения в котором инициализируется t1, а используется t2.

Phoenix

Корреляция между битовой маской и инициализированными структурами? Конечно ест.ь
Но я не понял, что это за путь, "в котором инициализируется t1, а используется t2."
Или всё такое сложное, что компилятор решает, что переменные используются все в каких-то случаях?
PS: если имеется ввиду корреляция param и ret — весьма слабая там корреляция. часть информации берётся вообще из других функций, а не передаётся в param

в общем, я понял, да. Наверно так и есть.

Phoenix

 
Почему битовая маска а не числа 0, 1, 2, ... и свитч?

может быть несколько структур инициализировано
 
Почему возвращаешь структуры разного типа если в результате нужно из них инициализировать одно и то же как бы?

структуры разные.
Пример сейчас поправлю.
 
как ты думаешь у тебя структуры в функцию передаются, через астрал?

я думал, что будет inline.
 
Почему гцц ругается на строчку t1.value = 10; где ты её инициализируешь, а не используешь?

Кажется там опечатка. Не знаю, как там строчка появилась. Он ругался на то, что я использую что-то неинициализированное. Поправил.

Phoenix

я вот про что


int func(T1* &t1, T1* pt1, T2* &t2, T2* pt2, int param)
{
if(param > 0)
{
t1 = pt1;
}
else
{
t1 = NULL;
}

if(param < 10)
{
t2 = pt2
}
else
{
t2 = NULL;
}
}

T1 t1;
T1 *pt1;
T2 t2;
T2 *pt2;

double b=0;

for(int i=0;i<100;i++)
{
func(pt1, &t1,pt2, &t2, i);

if(pt1 && pt2 == NULL)
{
b = (double)pt1->value;
}

else if(pt2 && pt1 == NULL)
{
b = pt2->value;
}
else if(pt2 && pt1)
{
b = pt2->value + pt1->value;
}

// do smth with a,b,c
}


Это ещё более запутанно получается.

Serab

if(pt1 && pt2 == NULL)
:aaa:
upd: а, там это не самое страшное

Phoenix

что не так? Мне это тоже не нравится, поэтому я и используют ret как битовую маску для этого.
Исходная задача: есть список действий "удалить", "создать" и т.п., в зависимости от ситуации, нужно сделать одно или несколько действий. func как раз и отвечает на вопрос, что сейчас нужно сделать. А тот код, что её вызывает должен потом эти действия сделать.

tamusyav

Имхо, нелишне здесь написать ответы на пару вопросов:
1. Язык C или C++? Если первое, то конструкторы обсуждать здесь бессмысленно; если второе, то почему нельзя хранить признак заполненности там же, где и сами значения, а не в отдельной переменной (к тому же битовой маске)?
2. Код пишется для компа или контроллера? Если для компа, то не вижу смысла сильно упираться в уменьшение занимаемой памяти и времени работы.
Мне кажется, что можно написать более элегантное решение. Какое именно - зависит от ответов на эти вопросы, а также, возможно, от еще какой-то существенной информации, которую ТС не сообщил.
PS. Строчки "ccc = t1.value;", на которую указывает gcc, в представленном коде нет.

Serab

не так — путаница в идиомах, почему бы не писать
if( pt1 && !pt2 )

или
if( pt1 != NULL && pt2 == NULL )

?

Serab

Исходная задача: есть список действий "удалить", "создать" и т.п., в зависимости от ситуации, нужно сделать одно или несколько действий. func как раз и отвечает на вопрос, что сейчас нужно сделать. А тот код, что её вызывает должен потом эти действия сделать.
тоже, кстати, было бы интересно услышать, как такое на Си пишут. Мне лично только дурацкие методы в голову лезут.
А хотя там плюсы формально.

Serab

Ну хотя вот наговнокодил, может в этом духе? Хотя Даркгрей сейчас скажет, что у меня преждевременная инкапусляция :grin:

enum ActionType {...};

struct Action {
enum ActionType type;
union ActionData { ... } data;
};

#define BUF_SIZE 10
void function(struct Action* actions, int* len, int param)
{
int j = 0;
...
struct Action newAction;
// init newAction
actions[j++] = newAction;

len = j;
}

void runAction(struct Action* action)
{
// run it somehow
}

///
struct Action buffer[BUF_SIZE];
int len;
function(buffer, &len, param);
for( int i = 0; i < len; ++i ) {
runAction(buffer + i);
}

Dasar

Хотя Даркгрей сейчас скажет, что у меня преждевременная инкапусляция
стиль решения в мелочах, конечно, больше на c++ похоже
вот так больше похоже на C

enum ActionType {...};

struct Action {
enum ActionType type;
union { ... } data;
};

#define BUF_SIZE 10
void function(struct Action* actions, struct Action** end, int param)
{
struct Action * current = actions;
...
// init newAction
//current->type = ..;
...
current++;


*end = current;
}

void runAction(struct Action* action)
{
// run it somehow
}

///
struct Action buffer[BUF_SIZE];
struct Action * end;
function(buffer, &end, param);
for( struct Action * current = buffer; current != end; ++current) {
runAction(current);
}

Phoenix

1. Язык C или C++?

c++. Но используется в основном ради шаблонов
Хранить вместе со структрами - это я второй вариант сделал, В общем-то похоже. Но когда часть структуры инициализировано, а часть нет - как-то опасно выглядит.
2. Код пишется для компа или контроллера? Если для компа, то не вижу смысла сильно упираться в уменьшение занимаемой памяти и времени работы.

для компа, но это будет тред, который будет занимать 100% времени одного ядра процессора. Там надо и в кэш влезть и лишних операций не делать.
Так что это ближе к контроллеру наверно, чем к компу.
"ps". поправил.

Phoenix

тиль решения в мелочах, конечно, больше на c++ похоже
может быть только одно действие "создать". И количество действий ограничено. Зачем это обобщение: отдельное поле "тип".
действия обрабатываются не последовательно, а вместе.
Т.е, например, будет дейсвтие "создать" и "удалить" и у них некоторые параметры совпадают, тогда будет вызвана функция "изменить", а не 2 действия.
Некоторый действия наоборот подавляют другие действия.(например "принудительно_удалить" подавит "создать")
Есть ещё некоторое количество флагов. Некоторый действия при некоторых флагах игнорируются.
Я наоборот хочу написать так, прыжков по минимуму было, а тут лишние if'ы же на пустом месте. (if(action->type == ...

Serab

указатель+длина — вполне себе распространенная идиома.

Dasar

указатель+длина — вполне себе распространенная идиома.
идиома нормальная, но вот индексное обращение в C реже используется, чаще поинтеры инкрементируются

Serab

ну вот судя по описанию у тебя какая-то хитрая логика и связь между этими действиями. И ты хочешь эту хитрость перенести в код. Неясно зачем, наоборот можно написать просто.
Про 100% работы на одном ядре... тут уже писали про профилировщик, напиши сначала как-нибудь _просто_, потом попрофилируй, посмотри, из-за чего тормоза, придумай более эффективный алгоритм или еще чего, если не будет устраивать имеющаяся производительность, может и так будет все ок...

Serab

Ну это все слишком мелкие тонкости, меня больше волнует интерфейс.

Phoenix

не очень понял. Вроде и так же всё просто. Есть функцию, которая возвращает "действия"("задания" другая функция их выполняет.
Просто для возврата из функции нескольких структур я сделал через ссылку и флаг, что они не нулевые. Чем это сложнее указателя и длины, а потом ещё кастов в зависимости от полей — я не понимаю,
Как тут уже подсказали, можно выделить в структуре ещё одно поле, которое будет флагом для этой структуры(поле is_null или как-то так).
Возможно тогда компилятор догадается, завтра попробую.
 Хотя по сути это ведь мало отличается от битовой маски — тоже возвращение через ссылку и флаг is_null для каждого поля.

Serab

ну вот мне оно кажется более сложным для понимания. Как минимум потому, что это нестандартно. Т.е. туманно оно все выглядит для того, кто первый раз сталкивается. Тем более с битовыми полями. Хотя может быть это из-за абстрактности имен (Т1, Т2 и т.д.)
Возможно тогда компилятор догадается, завтра попробую.
 Хотя по сути это ведь мало отличается от битовой маски — тоже возвращение через ссылку и флаг is_null для каждого поля.
Ну так у тебя связанные вещи размазаны на два параметра, причем один из параметров агрегирует эти флаги для некоторого количества других параметров. Это просто объективно сложно.
Может быть, решается именами

enum ActionType {
AT_Create = 1 << 1,
AT_Delete = 1 << 2,
AT_Modify = 1 << 3
};
int func(CreateParams* createParams, DeleteParams* deleteParams, ModifyParams* modifyParams, int* actionsToPerform, int param)
{
*actionsToPerform = 0;

*actionsToPerform |= AT_Create;
createParams->index = 7;

return 0;
}

...

if( actionsToPerform & AT_Modify ) {
// use modifyParams
}

может быть и ок. Но здесь я реально вижу неприятности с непроинициализированностью. Где-то придется обнулять, наверное. Т.е. надо просто четко специфицировать: что делает функция со структурой, для которой не будет установлен флаг. Имхо, логично, что она ее не трогает. Тогда тебе действительно надо просто во внешнем контексте инициализировать все структуры и все будет хорошо.
Минусы этого подхода: для добавления нового типа действий придется переделывать сигнатуру функции и *все* случаи ее вызова. А еще за корректность выполнения этих действий (например, за правильный порядок) отвечает каждый вызывающий. Т.е. если изменится логика, то придется переделывать еще кучу всего. А так бы пришлось только runAction переделать и саму функцию, создающую массив действий. Исходя из этого мне проще понять код со списком действий (я просто зову вот эту функцию, а потом вон ту тем более, что это стандартный подход.
Если здесь сами действия кристально чисты и очевидна связь между ними и очевидно, что новых видов появиться не может (даже теоретически: это все вопросы понимания кода то должно быть ок, конечно.
Оставить комментарий
Имя или ник:
Комментарий: