LINUX.ORG.RU

Глюк с инициализацией глобальных переменных (C/mingw)

 , , , ,


0

3

Всех приветствую!

Обнаружилось непонятное явление: после компиляции mingw32 и запуска под wine в программе не инициализируются глобальные переменные.

Более подробно:

Глобальные переменные:

bool line_mode = false;
enum {ORTHOGRAPHIC=0, PERSPECTIVE};
int projection_type=ORTHOGRAPHIC;

Некоторые ф-ции:

void reshape_cb (int w, int h)
{
    glViewport ( 0, 0, (GLint) w, (GLint) h ) ;
    WIDTH = w;
    HEIGHT = h;

       
    printf("projection_type = %d\n", projection_type);
    printf("line_mode = %d\n", line_mode);
    switch ( projection_type )
    {
      case PERSPECTIVE:
        printf("PERSPECTIVE\n");
        ...        
        break;

      case ORTHOGRAPHIC:
        printf("ORTHOGRAPHIC\n");
        ...
        break;
    };

    ...
    CHECK_GL_ERROR(); 
    printf("reshape()\n");
}
void keyboard_cb ( unsigned char key, int x, int y )
{
    switch ( key )
    {
      case 'l' :
      case 'L' :
      {
          line_mode = !line_mode;
          break;
      }

      case '1' :
      {
          projection_type = ORTHOGRAPHIC;
          reshape_cb(WIDTH, HEIGHT);
          break;
      }

      case '2' :
      {
          projection_type = PERSPECTIVE;
          reshape_cb(WIDTH, HEIGHT);
          break;
      }

      ...

    };

    glutPostRedisplay();
}

После запуска wine program.exe на экран выдается

projection_type = -1122261533
line_mode = 100
reshape()

При компиляции gcc все нормально:

projection_type = 0
line_mode = 0
ORTHOGRAPHIC
reshape()

Лечится это явление добавлением static:

static bool line_mode = false;
enum {ORTHOGRAPHIC=0, PERSPECTIVE};
static int projection_type=ORTHOGRAPHIC;

(или, возможно, инициализацией в ф-ции main) Тогда правильно работает и после mingw.

Кто-нибудь сталкивался с таким явлением? Почему это происходит?

Добавление.

Если после запуска wine program.exe нажать клавишу [2], то

projection_type = -1122261533
line_mode = 100
reshape()
НАЖАТИЕ КЛАВИШИ [2]
projection_type = 1
line_mode = 100
PERSPECTIVE
reshape()

видно значение projection_type инициализировалось как и задумано.

Тоже самое после gcc: ./program

projection_type = 0
line_mode = 0
ORTHOGRAPHIC
reshape()
НАЖАТИЕ КЛАВИШИ [2]
projection_type = 1
line_mode = 0
PERSPECTIVE
reshape()

PS Заранее благодарю за осмысленные ответы.

PS2 К сожалению, сделать маленький пример на выдачу этой ошибки пока не удалось. А вываливать кучу файлов сюда неуместно. Могу exe-шник дать для анализа явления.



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

если я прав, то дамп этой переменной в самом начале мейна должен ситуацию исправить. а также отказ от оптимизации, в том числе и на уровне сборщика.

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

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

Хватит нести чушь.

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

Я ж писал - пусть отладочные printf-ы расставит и поймает момент где там мусор записывается на место нуля. Может у него память бьётся или ещё что-то подобное. Он же уже выяснил что на старте проги там ноль как и положено, значит инициализация ни при чём.

И в любом случае «не хочет инициализировть» тут мимо - подобные инициализаторы прописываются в секцию данных бинарника статически, никакого кода они не генерят (если там только не вызов функций, которого у автора нет).

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

А то что не инициализировалось - заполняется нулями уже загрузчиком. Так что мусор в глобальной переменной это в любом случае где-то явно сделанная запись в неё именно мусора, а не «забыли».

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

пусть не принты понаставит, а сначала сбилдит с ключами -Wall и прочие. поскольку если он пишет мусор, то это это не просто так, и возможно ловится опциями компиляции.

а потом пускает под отладчиком, и ловит когда пишут в эту переменную через команду watch

https://www.sourceware.org/gdb/current/onlinedocs/gdb.html/Set-Watchpoints.html

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

сами хидеры не компилируют.

Я бы не был столь категоричен. Компилирование заголовка — дело вполне разумное и полезное чтобы убедиться, что заголовок включает все необходимые заголовки.

си это тебе не пытон какой-нить

Ну да, Си — это прошлый век. Массивов нет, модулей нет, одни костыли. Компилирование заголовков — это один из костылей.

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

Компилирование заголовка — дело вполне разумное и полезное чтобы убедиться, что заголовок включает все необходимые заголовки.

Что, кто-то реально вот именно так проблему self-sufficiency заголовков решает? На практике легко достигается включением заголовка первым в соответствующий .c/.cpp. Ну или в C-шник с юнит-тестами. А компилять заголовок сам по себе - только время впустую тратить, и электричество жечь, имхо.

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

На практике легко достигается включением заголовка первым в соответствующий .c/.cpp.

Нифига оно так не работает на развесистых либах, иначе инструменты вроде IWYU не появились бы.

anonymous
()

Я вангую, что у тебя в коде что-то портит эту переменную. Грепни по сорцам и ищи все места, где идёт обращение к ней. Можешь натыкать принтов до и после в каждое место, где идёт взаимодействие с ней. Ну или использовать отладчик.

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

Эхо-хо. Ошибка банальнейшая.

Обставил printf-ами,

запускаю wine program.exe

лог:

0080:fixme:hid:handle_IRP_MN_QUERY_ID Unhandled type 00000005
0080:fixme:hid:handle_IRP_MN_QUERY_ID Unhandled type 00000005
0080:fixme:hid:handle_IRP_MN_QUERY_ID Unhandled type 00000005
0080:fixme:hid:handle_IRP_MN_QUERY_ID Unhandled type 00000005
    ____  _  _ _____  _____    __
   (( ___ \\// ||_// ((   ))  ((
    \\_||  //  || \\  \\_//  \\_))  24.12.2024
#1 projection_type = 0
[INFO]: Start!
00e0:err:gdi:CreateDCW no driver found for L":0.0"
freeglut (Z:\home\bark\Works\olo\program.exe): fgPlatformInitialize: CreateDC failed, Screen s
ize info may be incorrect
This is quite likely caused by a bad '-display' parameter
[INFO]: freeGLUT version: 30000 init success!
00e0:fixme:wgl:X11DRV_wglChoosePixelFormatARB unused pfAttribFList
00e0:fixme:win:RegisterTouchWindow hwnd 000000000003004E, flags 0x3 stub!
[INFO]: Global ERRSHOW object initialized!
[INFO]: OpenGL version: 4.6.0 NVIDIA 390.157!
#2 projection_type = 0
[INFO]: GLEW version: 2.1.0 init success!
#3 projection_type = 0
[INFO]: Shader language version: 4.60 NVIDIA is support!
[INFO]: VAO support!
#4 projection_type = 0
[INFO]: 3D-texturing support!
#5 projection_type = 0
#6 projection_type = 0
[INFO]: Create glsl-program: id=1.
[INFO]: Loading VERTEX-shader type from 'shaders/cloud.vs'
[INFO]: VERTEX-shader id=2 compiled and attached.
[INFO]: Loading FRAGMENT-shader type from 'shaders/cloud.fs'
[INFO]: FRAGMENT-shader id=3 compiled and attached.
[INFO]: Linking glsl-program..
[INFO]: OK
[INFO]: Uniform locations dictionary was full.
[INFO]: Validation..
[INFO]: OK
#7 projection_type = 0
Create plane with 2 triangles
Reading 'Julius-Caesar.obj' mesh model
 - vertices:   25141
 - normals:    25141
 - tex coords: 1
 - triangles:  49790
Create obj mesh!
Loaded 49790 triangles!
#8 projection_type = 0
#9 projection_type = 0
#10 projection_type = 0
#11 projection_type = 0
#12 projection_type = 0
#13 projection_type = 0
#14 projection_type = 0
#15 projection_type = -1122261533
[INFO]: The scene was initialized. Ready to render..
#16 projection_type = -1122261533
#17 projection_type = -1122261533
#18 projection_type = -1122261533
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Debug message (0): Start OpenGL debugging
Source:   Application
Type:     Marker
Severity: NOTIFY
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
projection_type = -1122261533
line_mode = 100
reshape()

Портится в месте:

...
    prog_use(&sky);
    prog_set_uniform_mat4(&sky, "Model", &model);
    printf("#14 projection_type = %d\n", projection_type); 

    mat3_get_normal_matrix(&normalMat, &model);
    prog_set_uniform_mat3(&sky, "NormalMatrix",  &normalMat);
    printf("#15 projection_type = %d\n", projection_type); 
...

Функция mat3_get_normal_matrix:

void  mat3_get_normal_matrix(MAT3 *nm, MAT4 *model)
{
    float* M=model->M;
    float* N=nm->M;

    N[0] = M[0];  N[4] = M[4];  N[ 8] = M[8]; 
    N[1] = M[1];  N[5] = M[5];  N[ 9] = M[9]; 
    N[2] = M[2];  N[6] = M[6];  N[10] = M[10];
    mat3_transpose(nm);
}

не верная, т.к. nm массив float[9], а я пишу N[ 9] = M[9], N[10] = M[10], т.е. пишу за границами массива и порчу глобальные переменные

(Кстати, после gcc ничего подобного не происходит, т.е. портится что-то чего сразу не увидишь.

И static тоже лечит, т.е. projection_type будет всегда 0 в логе, не будет портится).

Правильная ф-ция:

void  mat3_get_normal_matrix(MAT3 *nm, MAT4 *model)
{
    float* M=model->M;
    float* N=nm->M;

    N[0] = M[0];  N[3] = M[4];  N[ 6] = M[8]; 
    N[1] = M[1];  N[4] = M[5];  N[ 7] = M[9]; 
    N[2] = M[2];  N[5] = M[6];  N[ 8] = M[10];
    mat3_transpose(nm);
}

PS Огромное всем спасибо за советы!

PS2 Собирал той же строкой, компиляцией с хедерами. Так что они не причем. Но я их сейчас уберу.

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

Убрал хедеры (*.h) из строки сборки, стало

x86_64-w64-mingw32.static-gcc -std=c11  dict.c utils.h utils.c mlib.c glsl_prog.c  glutils.c  plane.c objmesh.c   noise_tex3D.c arrstr.c errshow.c  program.c -D FREEGLUT_STATIC -D GLEW_STATIC -o program.exe -static-libgcc -L/home/bark/mxe/usr/x86_64-w64-mingw32.static/lib -lglut -lopengl32 -lglu32 -lglew32s -lwinmm -lgdi32

Скомпилировалось без проблем, сразу.

Хедеры при компиляции оказались избыточными.

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

Я вот что подумал, может глобальные всегда объявлять со static?

Так они станут более надежно защищены от случайных «повреждений».

Просто из за того что будут класться в иное (чем обычные, без static) место в исполняемом файле.

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

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

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

кстати во.

https://stackoverflow.com/questions/3375697/what-are-the-useful-gcc-flags-for-c

я вот забыл еще -Wextra, -pedantic присоветовать. но там и другие полезные флаги есть.

короче выбери и поставь полезные флаги и всегда с ними работай.

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

После исправления в mat3_get_normal_matrix

глобальные переменные больше не портятся. (еще портил bool line_mode ).

Этот баг был и после gcc, но он не был виден.

Увидеть эту перезапись можно только после mingw.

После gcc программа работала как будто записи за границу не было.

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

PS Огромное всем спасибо за советы!

а причина ошибки-то в чем? в том, что ты перешел от каких-то своих типов(видимо с размером массива) к указателям на float, без размера массива.

float* M=model->M;
float* N=nm->M;

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

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

Если у тебя кто-то повреждает память, то наоборот желательно чтобы это происходило максимально заметно, чтобы ты побыстрее смог это заметить и исправить. Так что эти рассуждения неверное. Но вообще я сомневаюсь что static может спрятать переменную от повреждений, у тебя просто так случайно совпало. Если бы ты normalMat и всё остальное тоже static объявил наверно она бы опять портилась.

А так да, переменные лучше объявлять static, если они не должны быть видимыми в другом модуле - чтобы не засорять глобальное пространство имён.

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

Проверил с неправильной

void  mat3_get_normal_matrix(MAT3 *nm, MAT4 *model)
{
    //float* M=model->M;
    //float* N=nm->M;

    nm->M[0] = model->M[0];  nm->M[4] = model->M[4];  nm->M[ 8] = model->M[8]; 
    nm->M[1] = model->M[1];  nm->M[5] = model->M[5];  nm->M[ 9] = model->M[9]; 
    nm->M[2] = model->M[2];  nm->M[6] = model->M[6];  nm->M[ 10] = model->M[10];
    mat3_transpose(nm);
}

Даже с -Wall никаких предупреждений о выходе за границы.

Перешел на float*, чтобы короче запись была.

PS

typedef struct MAT3
{
    union   {  
                struct {
                          float M00, M10, M20,    
                                M01, M11, M21,
                                M02, M12, M22;
                       };
                float M[9];
            };
} MAT3;
Gyros
() автор топика
Ответ на: комментарий от anonymous

Я и не утверждал, что виновен mingw.

Ошибка в моей программе проявлялась только после компиляции mingw.

После компиляции gcc программа работала как будто выхода за границы массива не было, т.е. повреждалось что-то др., а не глобальные переменные (что легко было обнаружить увидев их патологическое изменение)

Gyros
() автор топика
Последнее исправление: Gyros (всего исправлений: 1)
Ответ на: комментарий от Gyros
typedef struct MAT3
{
    union   {  
                struct {
                          float M00, M10, M20,    
                                M01, M11, M21,
                                M02, M12, M22;
                       };
                float M[9];
            };
} MAT3;

не понял зачем union вкладывать в структуру.

вообще зачем union. тут ты полагаешься на то, что размещение полей в структуре и элементов в массиве - одинаковое.

выбери один из способов описания. или

struct {…}

или массив.

typedef float MAT3[9]; //тут он точно проверит индексы статически.

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

у него все настолько нормально с типом MAT3 (а не union как таковым), что его никто не проверил статически на выход за границу массива. а так.. все нормально угу.

типы ващета не только для размещений данных в памяти, но и проверки корректности к ним доступа.

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

clang кстати проверяет при тех же опциях статические индексы. по крайней мере у меня :)

короче составлять статистику мне лень

такой текстик

typedef float Array9[9];

typedef union MAT3 {
    struct {
          float M00, M10, M20, M01, M11, M21, M02, M12, M22;
    };
    float M[9];
} MAT3;

void ff() {
    MAT3 lf; lf.M[10] = 0;
    Array9 lfff; lfff[10] = 0;
}

тут оба индекса неправильные, он варнинги дает, что индекс вне границы

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

Повторю ещё раз, с union (и вообще с процитированным описанием типа) у него всё нормально. А ты, в который раз, несёшь чушь.

Проверку доступа смотри тут:

https://godbolt.org/z/n46Wha6h4

к его структуре это никакого отношения не имеет.

firkax ★★★★★
()
Ответ на: комментарий от alysnix
typedef float Array9[9];

typedef union MAT3 {
    struct {
          float M00, M10, M20, M01, M11, M21, M02, M12, M22;
    };
    float M[9];
} MAT3;

void ff() {
    MAT3 lf; lf.M[10] = 0; <-----вот эта
    Array9 lfff; lfff[10] = 0;
}

кстати прикол. вплоть до clang-15 помеченная строка показывается с варнингом. а начиная с clang-16 - без.

опции - -Wpedantic -Wall -Wextra

это в godbolt. другая строка всегда с варнингом.

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

Я видел у некоторых в интернете, что делают так с union-ом структуру.

Мне казалось, что это для красоты и демонстрации возможности доступа к элементам в виде просто переменных или как к элементу массива.

Но теперь что-то тоже стал сомневаться зачем union, и вот эти M00, M01,..

У меня они только в

void mat3_transpose(MAT3 *m)
{
    SWAP(m->M10, m->M01, float);
    SWAP(m->M12, m->M21, float);
    SWAP(m->M20, m->M02, float);
}

используются.

Здесь как бы красиво, что видно как строки со столбцами меняются ролями.

Больше M00, M01, … нигде не используются.

И не представляю, где они еще могут понадобиться.

Может лучше сделать

typedef struct MAT3 {
     float M[9];
} MAT3;

проще и лаконичнне.

Gyros
() автор топика
Ответ на: комментарий от Gyros
typedef struct MAT3 {
     float M[9];
} MAT3;

struct это для структур, то есть типов с именованными полями произвольного типа.

если тебе просто нужен массив чисел - можно написать

typedef float Array9Float[9];

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

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

Эхо-хо. Ошибка банальнейшая. Обставил printf-ами

Таки рецепты «от дидов» оказались рабочими, несмотря на потоки сарказма от фантазёров и «любителей модного / молодёжного» ;) От себя добавлю что valgrind такое должен был поймать сразу, рекомендую.

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

Нифига оно так не работает на развесистых либах

Нам при codebase в несколько десятков MLOC - хватает.

иначе инструменты вроде IWYU не появились бы.

Я так понимаю там не столько за достаточность инклудов, сколько за их минимизацию борются.

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

А зачем вообще держать всё это в глобалах вместо явной передачи структуры содержащей состояние?

ты меня спрашиваешь ? а я не знаю..

я не автор топика и к MS(к дебилоидным ide c cmake) не имею отношений..

породить гоманду `gcc 1.h 2.h 3.h main1.c main2.c` можно только покусамшись MSVC и cmake.

причём ТС явным образом получал массу ругани в консоль. Но результат `вот вам a.out но он говно` решил перенести в ЛОР

MKuznetsov ★★★★★
()