Programing

C에서 .h 파일의 비정상적인 사용

lottogame 2020. 10. 16. 06:59
반응형

C에서 .h 파일의 비정상적인 사용


필터링에 대한 기사를 읽는 동안 .h파일을 사용하는 동안 이상한 점을 발견 했습니다. 계수 배열을 채우는 데 사용합니다.

#define N 100 // filter order
float h[N] = { #include "f1.h" }; //insert coefficients of filter
float x[N];
float y[N];

short my_FIR(short sample_data)
{
  float result = 0;

  for ( int i = N - 2 ; i >= 0 ; i-- )
  {
    x[i + 1] = x[i];
    y[i + 1] = y[i];
  }

  x[0] = (float)sample_data;

  for (int k = 0; k < N; k++)
  {
    result = result + x[k]*h[k];
  }
  y[0] = result;

  return ((short)result);
}

그래서, float h[N] = { #include "f1.h" };이런 식 으로 사용하는 것이 일반적인 관행 입니까?


같은 전 처리기 지시문 #include텍스트 대체를 수행하고 있습니다 ( GCC 내부 의 GNU cpp 문서 참조 ). 주석과 문자열 리터럴을 제외한 모든 위치에서 발생할 수 있습니다.

그러나 a #include#행의 공백이 아닌 첫 번째 문자로 있어야 합니다. 그래서 당신은 코딩 할 것입니다

float h[N] = {
  #include "f1.h"
};

원래 질문 #include에는 자체 줄이 없었으므로 잘못된 코드가 있습니다.

그것은 아닌 정상적인 연습 만입니다 허용 연습. 이 경우 .h사용 #include "f1.def"또는 #include "f1.data"... 이외의 다른 확장을 사용하는 것이 좋습니다 .

컴파일러에게 전처리 된 양식을 보여달라고 요청하십시오. GCC의 컴파일 gcc -C -E -Wall yoursource.c > yoursource.i생성에 편집기 또는 호출기와보기yoursource.i

실제로 이러한 데이터를 자체 소스 파일에 저장하는 것을 선호합니다. 그래서 대신 GNU awkh-data.c 와 같은 도구를 사용하여 자체 포함 파일 을 생성 할 것을 제안합니다 (파일 ...로 시작 하고 끝납니다 .). 그리고 그것이 상수 데이터 인 경우 더 잘 선언합니다 (읽기 상태에있을 수 있음). -Linux 와 같은 전용 세그먼트 ). 또한 포함 된 데이터가 크면 컴파일러가이를 최적화하는 데 시간이 걸릴 수 있습니다 (불필요하게) (그러면 최적화없이 빠르게 컴파일 할 수 있습니다 ).h-data.cconst float h[345] = {};const float h[].rodatah-data.c


따라서 float h [N] = {#include“f1.h”}; 이 방법?

정상은 아니지만 유효합니다 (컴파일러에서 허용됨).

사용의 장점 : 더 나은 솔루션을 생각하는 데 필요한 약간의 노력을 아끼지 않습니다.

단점 :

  • 코드의 WTF / SLOC 비율이 증가합니다.
  • 클라이언트 코드와 포함 된 코드 모두에서 비정상적인 구문을 도입합니다.
  • f1.h가 무엇을하는지 이해하려면 어떻게 사용되는지 살펴 봐야합니다 (즉,이 짐승을 설명하기 위해 프로젝트에 문서를 추가해야하거나 사람들이 코드를 읽어야하는지 알 수 있습니다. 수단-두 솔루션 모두 허용되지 않음)

이것은 코드를 작성하기 전에 추가로 20 분을 생각하는 데 소요되는 경우 중 하나이며, 프로젝트 수명 동안 코드와 개발자를 저주하는 데 수십 시간을 절약 할 수 있습니다.


이전 답변에서 이미 설명했듯이 일반적인 관행은 아니지만 유효한 관행입니다.

다음은 대체 솔루션입니다.

파일 f1.h :

#ifndef F1_H
#define F1_H

#define F1_ARRAY                   \
{                                  \
     0, 1, 2, 3, 4, 5, 6, 7, 8, 9, \
    10,11,12,13,14,15,16,17,18,19, \
    20,21,22,23,24,25,26,27,28,29, \
    30,31,32,33,34,35,36,37,38,39, \
    40,41,42,43,44,45,46,47,48,49, \
    50,51,52,53,54,55,56,57,58,59, \
    60,61,62,63,64,65,66,67,68,69, \
    70,71,72,73,74,75,76,77,78,79, \
    80,81,82,83,84,85,86,87,88,89, \
    90,91,92,93,94,95,96,97,98,99  \
}

// Values above used as an example

#endif

파일 f1.c :

#include "f1.h"

float h[] = F1_ARRAY;

#define N (sizeof(h)/sizeof(*h))

...

아니, 정상적인 관행이 아닙니다.

이러한 형식을 직접 사용하는 것에는 이점이 거의 없습니다 . 대신 데이터가 별도의 소스 파일에서 생성되거나이 경우 최소한 완전한 정의가 형성 될 수 있습니다.


: 랜덤 장소에있는 파일을 포함하여 포함하는 "패턴"그러나이 X-매크로 , 같은 것과 같은가 .

X-macro의 용도는 컬렉션을 한 번 정의하고 다양한 장소에서 사용하는 것입니다. 전체의 일관성을 보장하는 단일 정의. 사소한 예로서 다음을 고려하십시오.

// def.inc
MYPROJECT_DEF_MACRO(Error,   Red,    0xff0000)
MYPROJECT_DEF_MACRO(Warning, Orange, 0xffa500)
MYPROJECT_DEF_MACRO(Correct, Green,  0x7fff00)

이제 여러 가지 방법으로 사용할 수 있습니다.

// MessageCategory.hpp
#ifndef MYPROJECT_MESSAGE_CATEGORY_HPP_INCLUDED
#define MYPROJECT_MESSAGE_CATEGORY_HPP_INCLUDED

namespace myproject {

    enum class MessageCategory {
#   define MYPROJECT_DEF_MACRO(Name_, dummy0_, dummy1_) Name_,
#   include "def.inc"
#   undef MYPROJECT_DEF_MACRO
    NumberOfMessageCategories
    }; // enum class MessageCategory

    enum class MessageColor {
#   define MYPROJECT_DEF_MACRO(dumm0_, Color_, dummy1_) Color_,
#   include "def.inc"
#   undef MYPROJECT_DEF_MACRO
    NumberOfMessageColors
    }; // enum class MessageColor

    MessageColor getAssociatedColorName(MessageCategory category);

    RGBColor getAssociatedColorCode(MessageCategory category);

} // namespace myproject

#endif // MYPROJECT_MESSAGE_CATEGORY_HPP_INCLUDED

오래 전에 사람들은 전처리기를 과도하게 사용했습니다. 예를 들어 사람들이 다음을 수행 할 수 있도록 설계된 XPM 파일 형식참조하십시오 .

#include "myimage.xpm"

그들의 C 코드에서.

더 이상 좋은 것으로 간주되지 않습니다.

같은 영업 이익의 코드 외모 C나에 대한 이야기를 할 수 있도록C

전처리기를 과도하게 사용하는 이유는 무엇입니까?

전 처리기 #include지시문은 소스 코드를 포함하기위한 것입니다. 이 경우와 OP의 경우 실제 소스 코드가 아니라 data 입니다.

왜 나쁜 것으로 간주됩니까?

그것은 매우 유연하지 않기 때문입니다 . 전체 응용 프로그램을 다시 컴파일하지 않고는 이미지를 변경할 수 없습니다. 컴파일 할 수없는 코드를 생성하므로 이름이 같은 두 개의 이미지도 포함 할 수 없습니다. OP의 경우 응용 프로그램을 다시 컴파일하지 않고는 데이터를 변경할 수 없습니다.

또 다른 문제는 데이터와 소스 코드 사이에 긴밀한 결합을 생성한다는 것 입니다. 예를 들어 데이터 파일에는 N소스 코드 파일에 정의 된 매크로에 지정된 수 이상의 값이 포함되어야합니다 .

긴밀한 결합은 또한 데이터에 형식을 적용합니다. 예를 들어 10x10 행렬 값을 저장하려는 경우 소스 코드에서 단일 차원 배열 또는 2 차원 배열을 사용하도록 선택할 수 있습니다. 한 형식에서 다른 형식으로 전환하면 데이터 파일이 변경됩니다.

이러한 문제 데이터로드 되어 쉽게 해결 표준 I / O 기능을 이용하여. 기본 이미지를 포함해야하는 경우 소스 코드의 이미지에 대한 기본 경로제공 할 수 있습니다 . 이렇게하면 최소한 사용자가이 값을 변경 #define하거나 ( -D컴파일시 또는 옵션을 통해 ) 다시 컴파일 할 필요없이 이미지 파일을 업데이트 할 수 있습니다.

In the OP's case, its code would be more reusable if the FIR coeficients and x, y vectors where passed as arguments. You could create a structto hold togeteher these values. The code would not be inefficient, and it would become reusable even with other coeficients. The coeficients could be loaded at startup from a default file unless the user pass a command line parameter overriding the file path. This would remove the need for any global variables and makes the intentions of the programmer explicit. You could even use the same FIR function in two threads, provided each thread has got its own struct.

When is it acceptable ?

데이터의 동적로드를 수행 할 수없는 경우. 이 경우 데이터를 정적으로로드 해야하며 이러한 기술을 사용해야합니다.

파일에 대한 액세스 권한이 없다는 것은 매우 제한된 플랫폼 에서 프로그래밍하고 있다는 것을 의미 하므로 트레이드 오프를 수행해야합니다. 예를 들어 코드가 마이크로 컨트롤러에서 실행되는 경우입니다.

그러나이 경우에도 C반 형식 파일의 부동 소수점 값을 포함하는 대신 실제 소스 파일 을 만드는 것을 선호 합니다.

예를 들어 C반 형식화 된 데이터 파일이 아닌 계수를 반환 하는 실제 함수를 제공 합니다. C함수는 두 개의 다른 파일에서 정의 할 수 있습니다. 하나는 개발 목적으로 I / O를 사용하고 다른 하나는 릴리스 빌드를 위해 정적 데이터를 반환합니다. 조건에 따라 올바른 소스 파일을 컴파일합니다.


때때로 소스 코드가 포함 된 다른 파일을 기반으로 .C 파일을 생성하기 위해 외부 도구를 사용하거나, 생성 도구에 고정 된 양의 코드가 포함 된 C 파일을 외부 도구로 생성하거나, #include다양한 "비정상적인"방식으로 지시. 이러한 접근 방식 중 후자는 비록 icky이긴하지만 종종 가장 악한 것이 아닐 수 있습니다.

I would suggest avoiding the use of the .h suffix for files which do not abide by the normal conventions associated with header files (e.g. by including method definitions, allocating space, requiring an unusual inclusion context (e.g. in the middle of a method), requiring multiple inclusion with different macros defined, etc. I also generally avoid using .c or .cpp for files which are incorporated into other files via #include unless those files are primarily used standalone [I might in some cases e.g. have a file fooDebug.c containing #define SPECIAL_FOO_DEBUG_VERSION[newline]`#include "foo.c"`` if I wish to have two object files with different names generated from the same source, and one of them is affirmatively the "normal" version.]

My normal practice is to use .i as the suffix for either human-generated or machine-generated files that are designed to be included, but in usual ways, from other C or C++ source files; if files are machine-generated, I will generally have the generation tool include as the first line a comment identifying the tool used to create it.

BTW, one trick where I've used this was when I wanted to allow a program to be built using just a batch file, without any third-party tools, but wanted to count how many times it was built. In my batch file, I included echo +1 >> vercount.i; then in file vercount.c, if I recall correctly:

const int build_count = 0
#include "vercount.i"
;

The net effect is that I get a value which increments on every build without having to rely upon any third-party tools to produce it.


When the preprocessor finds the #include directive it simply opens the file specified and inserts the content of it, as though the content of the file would have been written at the location of the directive.


As already said in the comments this is not normal practice. If I see such code, I try to refactor it.

For example f1.h could look like this

#ifndef _f1_h_
#define _f1_h_

#ifdef N
float h[N] = {
    // content ...
}

#endif // N

#endif // _f1_h_

And the .c file:

#define N 100 // filter order
#include “f1.h”

float x[N];
float y[N];
// ...

This seems a bit more normal to me - although the above code could be improved further still (eliminating globals for example).


Adding to what everyone else said - the contents of f1.h must be like this:

20.0f, 40.2f,
100f, 12.40f
-122,
0

Because the text in f1.h is going to initialize array in question!

Yes, it may have comments, other function or macro usage, expressions etc.


It is normal practice for me.

The preprocessor allows you to split a source file into as many chunks as you like, which are assembled by #include directives.

It makes a lot of sense when you don't want to clutter the code with lengthy/not-to-be-read sections such as data initializations. As it turns out, my record "array initialization" file is 11000 lines long.

I also use them when some parts of the code are automatically generated by some external tool: it is very convenient to have the tool just generate his chunks, and include them in the rest of the code written by hand.

I have a few such inclusions for some functions that have several alternative implementations depending on the processor, some of them using inline assembly. The inclusions make the code more manageable.

By tradition, the #include directive has been used to include header files, i.e. sets of declarations that expose an API. But nothing mandates that.


I read people want to refactor and saying that this is evil. Still I used in some cases. As some persons said this is a preprocesor directive so is including the content of file. Here's a case where I used: building random numbers. I build random numbers and I dont want to do this each time I compile neither in run time. So another program (usually a script) just fill a file with the generated numbers which are included. This avoids copying by hand, this allows easily changing the numbers, the algorithm which generates them and other niceties. You cannot blame easily the practice, in that case it is simply the right way.


I used the OP's technique of putting an include file for the data initialization portion of a variable declaration for quite some time. Just like the OP, the included file was generated.

I isolated the generated .h files into a separate folder so they could be easily identified:

#include "gensrc/myfile.h"

This scheme fell apart when I started to use Eclipse. Eclipse syntax checking was not sophisticated enough to handle this. It would react by reporting syntax errors where there were none.

I reported samples to Eclipse mailing list, but there did not seem to be much interest in "fixing" the syntax checking.

I changed my code generator to take additional arguments so it could generated that entire variable declaration, not just the data. Now it generates syntactically correct include files.

Even if I was not using Eclipse, I think it is a better solution.


In the Linux kernel, I found an example that is, IMO, beautiful. If you look at the cgroup.h header file

http://lxr.free-electrons.com/source/include/linux/cgroup.h

you can find the directive #include <linux/cgroup_subsys.h> used twice, after different definitions of the macro SUBSYS(_x); this macro is used inside cgroup_subsys.h, to declare several names of Linux cgroups (if you're not familiar with cgroups, they are user-friendly interfaces Linux offers, which must be initialized at system boot).

In the code snippet

#define SUBSYS(_x) _x ## _cgrp_id,
enum cgroup_subsys_id {
#include <linux/cgroup_subsys.h>
   CGROUP_SUBSYS_COUNT,
};
#undef SUBSYS

each SUBSYS(_x) declared in cgroup_subsys.h becomes an element of the type enum cgroup_subsys_id, while in the code snippet

#define SUBSYS(_x) extern struct cgroup_subsys _x ## _cgrp_subsys;
#include <linux/cgroup_subsys.h>
#undef SUBSYS

each SUBSYS(_x) becomes the declaration of a variable of type struct cgroup_subsys.

In this way, kernel programmers can add cgroups by modifying only cgroup_subsys.h, while the pre-processor will automatically add the related enumeration values/declarations in the initialization files.

참고URL : https://stackoverflow.com/questions/26843347/unusual-usage-of-h-file-in-c

반응형