LINUX.ORG.RU

Без -O1/-O2/-O3/-Os не видит функции из *.h

 , ,


1

2

Скачал http://aluigi.altervista.org/papers/unxwb.zip, пытаюсь собрать. Там основной файл unxwb.c и 4 *.h файла, подключаемые в начале:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <zlib.h>
#include <stdint.h>
#include "show_dump.h"
#include "myxact.h"
#include "mywav.h"
#include "xma_header.h"

В этих заголовочных файлах — определения типов и структур и функции, вызываемые из unxwb.c. Не заголовки, а именно функции.

1. При запуске «gcc unxwb.c» получаю серию ошибок линковки вида:

unxwb.c:(.text+0xb74): undefined reference to `xma_le16'
unxwb.c:(.text+0xb8a): undefined reference to `xma_le32'
Но функции xma_le16 и xma_le32 присутствуют в xma_header.h:
inline unsigned xma_le16(unsigned n) {
   ...
}
    
inline unsigned xma_le32(unsigned n) {
   ...
}

В чём дело, почему не видит эти функции?

Дополнение: Нашлась более новая версия: http://blog.ssokolow.com/archives/2014/04/21/extracting-music-from-xwb-files-... Из её мейкфайла выяснил, что с ключом -O2 нормально собирается и работает. То же с -O1, -O3, -Ofast, -Og и -Os. По умолчанию -O0 — с ним не работают. Что такого делают -O1, -O2, -O3, -Ofast, -Og и -Os, что начинает линковаться правильно?

2. Кроме того, на:

fprintf(fdinfo, "\n"
            "- bank name         %.*s\n"
            sizeof(wavebankdata.szBankName), wavebankdata.szBankName);
выдаёт предупреждение:
nxwb.c:470:25: warning: field precision specifier ‘.*’ expects argument of type ‘int’, but argument 5 has type ‘long unsigned int’ [-Wformat=]
         fprintf(fdinfo, "\n"
                         ^
Я правильно понимаю, что проблема из-за того, что программа писалась под 32 бита, а собираю на 64-битной системе? Достаточно ли поставить (int) перед sizeof, или нужны более глубокие исправления?

Не вызваны ли ошибки линковки тем же?

Дополнение: -O2 и другие -O... на это не влияют.

Полный вывод «gcc -v unxwb.c -lz»: https://pastebin.com/bd0zY68f (Почти все системные заголовочные файлы и библиотеки нормально подставляются параметрами по умолчанию, явно нужно указывать только -lz для zlib.)

Ответ: При сборке без оптимизаций (-O0) функции с атрибутом inline не подключаются. «By design.» Любого из -O1, -O2, -O3, -Ofast, -Og или -Os достаточно.

«gcc -O2 unxwb.c -lz» нормально собирает программу, и она работает.

★★★★★

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

-std=c89

Программа не собирается как C89, так как использует комментарии //

inline в смысле C99 или C11.

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

Спасибо. Но на long unsigned int продолжает ругаться.

И насколько я могу судить, патч только улучшает поддержку ADPCM. Нормальная сборка обеспечивается мейкфайлом:

CC = gcc
CFLAGS = -O2
SHELL = /bin/sh
srcdir = .
FILES = unxwb.c mywav.h myxact.h xma_header.h
.SUFFIXES:
.PHONY: build multiarch
build: unxwb
multiarch: unxwb.x86_64 unxwb.x86
unxwb unxwb.x86_64: $(FILES)
	$(CC) -I$(srcdir) $(CFLAGS) $< -o $@ -lz
unxwb.x86: $(FILES)
	$(CC) -I$(srcdir) $(CFLAGS) $< -o $@ -lz -m32
Как это работает, интересно?

А! Достаточно было -O2.

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

там есть make. ты его используешь?

Да, make всё нормально собрал. Первое сообщение послал случайно, не дописав, и не заметил.

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

Нормально собирается и с остальными оптимизациями: -O1, O3 и -Os. Не собирается только в состоянии по умолчанию, которое соответствует -O0. Что в нём такого, что не может найти эти функции?

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

Перебрал все ключи, перечисленные для -O1 здесь: https://gcc.gnu.org/onlinedocs/gcc-5.4.0/gcc/Optimize-Options.html Ни один сам по себе не обеспечил сборку. Сделал

$ gcc -O1 -Q --help=optimizers | grep enabled
  -faggressive-loop-optimizations 	[enabled]
  -fasynchronous-unwind-tables 		[enabled]
  -fauto-inc-dec              		[enabled]
  -fbranch-count-reg          		[enabled]
  -fcombine-stack-adjustments 		[enabled]
  -fcompare-elim              		[enabled]
  -fcprop-registers           		[enabled]
  -fdce                       		[enabled]
  -fdefer-pop                 		[enabled]
  -fdelete-null-pointer-checks 		[enabled]
  -fdse                       		[enabled]
  -fearly-inlining            		[enabled]
  -fforward-propagate         		[enabled]
  -ffunction-cse              		[enabled]
  -fgcse-lm                   		[enabled]
  -fguess-branch-probability  		[enabled]
  -fif-conversion             		[enabled]
  -fif-conversion2            		[enabled]
  -finline                    		[enabled]
  -finline-atomics            		[enabled]
  -finline-functions-called-once 	[enabled]
  -fipa-profile               		[enabled]
  -fipa-pure-const            		[enabled]
  -fipa-reference             		[enabled]
  -fira-hoist-pressure        		[enabled]
  -fira-share-save-slots      		[enabled]
  -fira-share-spill-slots     		[enabled]
  -fivopts                    		[enabled]
  -fjump-tables               		[enabled]
  -flifetime-dse              		[enabled]
  -fmath-errno                		[enabled]
  -fmove-loop-invariants      		[enabled]
  -fpeephole                  		[enabled]
  -fprefetch-loop-arrays      		[enabled]
  -frename-registers          		[enabled]
  -frtti                      		[enabled]
  -fsched-critical-path-heuristic 	[enabled]
  -fsched-dep-count-heuristic 		[enabled]
  -fsched-group-heuristic     		[enabled]
  -fsched-interblock          		[enabled]
  -fsched-last-insn-heuristic 		[enabled]
  -fsched-rank-heuristic      		[enabled]
  -fsched-spec                		[enabled]
  -fsched-spec-insn-heuristic 		[enabled]
  -fsched-stalled-insns-dep   		[enabled]
  -fschedule-fusion           		[enabled]
  -fshort-enums               		[enabled]
  -fshrink-wrap               		[enabled]
  -fsigned-zeros              		[enabled]
  -fsplit-ivs-in-unroller     		[enabled]
  -fsplit-wide-types          		[enabled]
  -fssa-phiopt                		[enabled]
  -fstdarg-opt                		[enabled]
  -fstrict-volatile-bitfields 		[enabled]
  -fno-threadsafe-statics     		[enabled]
  -ftrapping-math             		[enabled]
  -ftree-bit-ccp              		[enabled]
  -ftree-ccp                  		[enabled]
  -ftree-ch                   		[enabled]
  -ftree-coalesce-vars        		[enabled]
  -ftree-copy-prop            		[enabled]
  -ftree-copyrename           		[enabled]
  -ftree-cselim               		[enabled]
  -ftree-dce                  		[enabled]
  -ftree-dominator-opts       		[enabled]
  -ftree-dse                  		[enabled]
  -ftree-forwprop             		[enabled]
  -ftree-fre                  		[enabled]
  -ftree-loop-if-convert      		[enabled]
  -ftree-loop-im              		[enabled]
  -ftree-loop-ivcanon         		[enabled]
  -ftree-loop-optimize        		[enabled]
  -ftree-phiprop              		[enabled]
  -ftree-pta                  		[enabled]
  -ftree-reassoc              		[enabled]
  -ftree-scev-cprop           		[enabled]
  -ftree-sink                 		[enabled]
  -ftree-slsr                 		[enabled]
  -ftree-sra                  		[enabled]
  -ftree-ter                  		[enabled]
  -fvar-tracking              		[enabled]
  -fvar-tracking-assignments  		[enabled]
  -fweb                       		[enabled]
Из них по одному тоже ни один не помогает.

И когда задал в командной строке весь список вместо -O1 — тоже не сработало.

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

Смотри gcc -E

С этого начал. Все заголовочные файлы правильно вставляются.

Возможно, стоит репортнуть

Не уверен, что версия ещё поддерживается. Хотя это мысль — сравнить с GCC поновее. Но смогу только вечером.

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

inline в Си такой, что ожидается, что ты и функцию в отдельной единице компиляции предоставишь, т.к. решение инлайнить или нет всё же принимает компилятор.

Меняй на static inline.

a1batross ★★★★★
()

Прочёл комменты.

Немного не так всё. У Вас не происходит инлайнинг функций без оптимизации. Gcc так и работает. Либо используйте для отладочной версии:

static inline __attribute__((always_inline)) int fn(const char *s) {

}

,

Т.е., атрибут всегда подставлять тело ф-ии, либо используйте не инлайн-функции а макросы. В отличие от инлайн-функции они будут развёрнуты в тело кода всегда.

Moisha_Liberman ★★
()
Ответ на: Прочёл комменты. от Moisha_Liberman

Либо (как вариант).

Либо описываете эти ф-ии как

extern inline int some_func()

В этом случае gcc воспримет их как отдельную единицу трансляции и оформит соответствующим образом. Ну и, соответственно, на линковку ld передаст их правильно.

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

https://gcc.gnu.org/onlinedocs/gcc/Inline.html

Все очень даже ясно описано. Как правило inline «в чистом виде» не используется в include-файлах для C. В этом случае рядом либо static (см. коммет выше), либо атрибуты типа always_inline.

Разница же в том, что при отстутствии оптимизации формально надо бы функцию представить как extern - но тогда все не соберется из-за повторов кода в модулях с тем же include. Посему компилировать ее тоже грех.

Замечу, что в раличных релизах поведение разное. Также как поведение C++ компилятора (для него многократная компиялция кода грехом не является - линкер разберется, хотя и не без приколов иногда). Поэтому получаем:

$ gcc -o inline inline.c
/tmp/ccc8kGVf.o: In function `main':
inline.c:(.text+0x15): undefined reference to `xma_le16'
collect2: error: ld returned 1 exit status
$ gcc -fgnu89-inline -o inline inline.c
$ g++ -o inline inline.c
$

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

Разница же в том, что при отстутствии оптимизации формально надо бы функцию представить как extern - но тогда все не соберется из-за повторов кода в модулях с тем же include. Посему компилировать ее тоже грех.

#pragma once хорошо защищает от повторов инклюдов.

Как правило inline «в чистом виде» не используется в include-файлах для C. В этом случае рядом либо static (см. коммет выше), либо атрибуты типа always_inline.

Нормально используется. Просто, квалификатор inline это «пожелание» со стороны программиста компилятору gcc типа, «заинлайни, если сможешь», а attribute ((__always_inline__)) это прямая директива комплилятору типа «инлайни и не умничай».

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

квалификатор inline это «пожелание» со стороны программиста компилятору gcc типа, «заинлайни, если сможешь»,
а attribute ((__always_inline__)) это прямая директива комплилятору типа «инлайни и не умничай».

А что даёт «extern inline»?

И почему не линкуется, если после препроцессинга все недостающие функции всё равно оказываются в файле?

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

Если коротко...

А что даёт «extern inline»?

И почему не линкуется, если после препроцессинга все недостающие функции всё равно оказываются в файле?

Идея inline ф-ий заключается в их подстановке при компиляции и линковке. При отсутствии оптимизаций gcc гонит всё «как есть» линкеру, не выстраивая оптимизированную таблицу перекрёстных ссылок. При отсутствии оптимизаций (-О0 же!) ни каких оптимизаций не производится и инлайнинга тоже. Ф-я не подставляется без оптимизаций.

Тут довольно такой... «грязный хак» используется. Дело в том, что gcc работает с некими TU (translation units). Указывая extern мы ему говорим что это не инлайн ни фига, а некий внешний TU. В этом случае эта TU будет в глобальной зоне видимости. Без указания extern, если мы глянем на таблицу экспортируемых символов, то ни чего там не увидим. С квалификатором extern ф-я будет экспортирована и доступна из любого места программы. В этом случае линкер (ld) найдёт её без труда.

Иначе, если мы определяем ф-ю как inline, то её адрес без оптимизации не будет определён (она же не подставляется). Чаще всего пофиг какая это ф-я инлайн или нет, её адрес должен оставаться неизменным. А тут у нас просто нет подстановки. Линкер не знает на что ему ссылаться, так что вот Вам и прилетела ошибка линковки.

Всё логично, в принципе.

Ещё добавлю что для версии, собираемой с -О0 можно использовать флаги gcc типа -fkeep-inline-functions и -fgnu89-inline. Но я не проверял их работу.

UPD. Само по себе поведение ф-ий inline описано в стандарте http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1124.pdf (ISO/IEC 9899:1999б, раздел 6.7.4).

Moisha_Liberman ★★
()
Последнее исправление: Moisha_Liberman (всего исправлений: 1)
Ответ на: комментарий от Moisha_Liberman
#pragma once хорошо защищает от повторов инклюдов.

И для различных модулей и единиц трансляции? Это что-то новенькое.

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

Не...

Было сказано:

тогда все не соберется из-за повторов кода в модулях с тем же include.

Тогда блокируйте «повторы кода в тех же include». Как — уже сказал. Если просто и по-русски, то не включайте одни и те же include по нескольку раз в проект. Разок включили и хорош.

Moisha_Liberman ★★
()
Ответ на: Не... от Moisha_Liberman

Точно-точно. stdio.h только в одном файле и ни-ни в других. stdlib.h только в другом файле. xma_header.h в третьем. А еще лучше все в одном файле и никаких include вообще. Все едино только разок включить можно. Нобелевка однозначно.

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

Вам бы санитаров вызвать...

Точно-точно. stdio.h только в одном файле и ни-ни в других.

Вам бы, милейший, врачу что ли показаться... Не задумывались?

Ну хорошо. Давайте вместе с Вами откроем https://www.gnu.org/software/m68hc11/examples/stdio_8h-source.html и удивимся?

Смотрите, Вас ни на какие мысли не наталкивают строки оттуда:

#ifndef _STDIO_H_

#define _STDIO_H_

#endif /* _STDIO_H_ */

Так вот. Открою Вам страшную тайну. Эти строки называются «include guard» или «header guard». Это абсолютно точно (я это Вам гарантирую!), тоже самое что и #pragma once. Разве что использование #pragma once чуток лучше из-за отсутствия даже потенциальной коллизии имён, текста набирать меньше, да и компиль сам умеет имена разруливать.

Но в обоих случаях это защита от дурака, везде сующего по сто раз одни и те же инклюды. Впрочем, дурак об этом даже не догадывается. Ему без надобности.

Тот самый случай когда компиль умнее погроммиста.

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

Ещё добавлю что для версии, собираемой с -О0 можно использовать флаги gcc типа -fkeep-inline-functions

С этого начал. Не помогло.

и -fgnu89-inline.

А вот с ним всё работает.

Идея inline ф-ий заключается в их подстановке при компиляции и линковке. При отсутствии оптимизаций gcc гонит всё «как есть» линкеру, не выстраивая оптимизированную таблицу перекрёстных ссылок. При отсутствии оптимизаций (-О0 же!) ни каких оптимизаций не производится и инлайнинга тоже. Ф-я не подставляется без оптимизаций.

Я с самого начала прогнал «gcc -E unxwb.c > preprocessed.c»; убедился, что все инклуды в нём есть, и все 4 инлайнемых функции присутствуют; затем попытался собрать «gcc preprocessed.c -lz». В результате получил те же ошибки — не нашло инлайн-функции.

То есть, если функции приписать атрибут inline, при сборке без оптимизаций она будет выброшена, и такое поведение соответствует замыслу авторов-разработчиков?

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

Да.

По поводу ключей и кода я уже сам проверил, работает с -fgnu98-inline. Никогда особо не интересовался и этому есть причина (про неё чуть ниже).

То есть, если функции приписать атрибут inline, при сборке без оптимизаций она будет выброшена, и такое поведение соответствует замыслу авторов-разработчиков?

Как ни странно, но и здесь ответ «да». Здесь своя логика, насколько я её понимаю.

Просто, исторически (почему, собственно, по инлайнингу я например и не убиваюсь), сложилось так, что в основном использовались и используются макросы. Инлайнинг это... так. По сути, если компиль видит inline, то это для него не более чем такое «пожелание». Которое он может и проигнорировать вообще-то, т.е. не факт что инлайнинг будет произведён при оптимизации. Т.е., если функция мелкая, вызывается часто, то возможно что компиль её развернёт в тело кода. А возможно что и нет. Бабка надвое сказала. В этом случае (если код развёрнут) мы получим ускорение кода, но бОльший размер объектника. Т.е., по своей сути это уже оптимизация. Без оптимизации (-О0) это без оптимизации. «Как есть», так и получите. Компиль такие функции не выбрасывает, т.е. dead code elimination не производится, т.к. и это тоже оптимизация, а мы просили вообе без оптимизаций. Ф-я есть в коде, но ссылок на неё нет. Без выведения этой ф-ии в глобальное пространство имён (см. про extern выше и чуть ниже) ссылки на ф-ю не будет.

По сути, инлайнинг это уже когда у нас есть рабочий и отлаженный код, который работает. Только после этого я начинал бы расставлять inline. Именно для попыток оптимизации. Не ранее. Ну либо (если мне нужен был бы отладочный код с инлайнингом), то attribute ((__always_inline__)).

На месте того анонима, который тут чёт начал про танслейшн юниты гундеть, я бы например, докопался до того момента, когда я Вам порекомендовал поменять просто inline на extern inline. Собственно, это прямо противоположно тому, что рекомендует стандарт в п. 6.7.4 (ссылка выше). Но, в принципе, так можно для отладки, временно. Т.е., вывести из локального пространства имён и ввести в глобальное пространство имён, чисто на время отладки, временно, т.к. по сути, там уже инлайнингом и не пахнет. Тогда смысл использовать inline? Собственно, именно поэтому я и назвал этот вариант решения «грязным хаком». Стандарт не зря писан и его нарушать нужно с умом и аккуратно, а лучше вообще не нарушать.

Собственно, почему я по инлайнингу и не убиваюсь-то. В своём коде я отрываюсь именно по макросам. Во-первых, макросы в gcc это охрененно проработанная тема (те же function-like macros, variadic argument macros, etc.). Тут уже сложно что-нибудь придумать. Во-вторых, макрос это просто блок кода, который всегда, я подчеркну это — всегда будет развёрнут в тело исходника и ещё на этапе препроцессинга, т.е., когда компиль вообще ни чего не знает о пространствах имён, об оптимизациях и ещё до этапа синтаксического анализа. По крайней мере, компиль развернёт все макросы вне зависимости от того, отладочный это код или релиз. Временами так проще, но да, надо думать что в совать в макросы, где будет выигрыш по скорости и пролёт по размеру кода, а от чего воздержаться. Правда, макросами тоже надо умеючи пользоваться, но это не повод изобретать инлайнинг. =)

Инлайнинг, кстати, добавили в С99 типа в С++ уже есть, а мы-то чё как дурни без подарка. С тех пор и путается народ (кто только на эти грабли не налетал).

Ну вот, как-то вот так, в общем.

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