[Makefile]

general_var:= my-value

target: dep1 dep2

    <do something> $(general_var)

위의 makefile이 단독으로 사용되는 경우는, 전혀 문제가 되지 않는다. 그렇지만, makefile이 서로 서로 include되는 등의 복잡한 system에서는

경우에 따라, 위의 makefile이 parsing된 이후, general_var 의 값이 다른 값으로 바뀌게 될 수도 있다.(include된 다른 make file에서...)

이럴 경우, 'target'의 command section이 수행될 때는 'general_var'가 이미 다른 값으로 치환된 이후이기 때문에, 기대와 다른 동작을 하게 될 수 있다.

('blog의 variable expansion at command section on GNUMake 참조)

이것을 막기 위해서는 아래와 같은 형태로 make file을 구성해야 한다. 


[Makefile]

general_var:= my-value

target: private_var := $(general_var)

target: dep1 dep2

    <do something> $(private_var)

위의 makefile은, makefile이 parsing되고 target을 만들기 위한 rule이 수행되기 전에 아래와 같은 상태가 된다.


[Makefile]

general_var:= my-value

target: private_var := my-value

target: dep1 dep2

    <do something> $(private_var)


따라서, target 의 command section이 수행되기 전에, dependency section이 수행되면서 private_var값이 my-value로 set 되고

target 의 command section은 기대했던 'my-value' 값을 가지고 수행된다.

단, 아래와 같은 syntax는 "*** commands commence before first target.  Stop." error를 발생시키니 주의해야 한다.

(즉 dependency section에서 variable assignment를 수행할 경우, command section과 함께 있으면 안된다.) 


general_var:= my-value

target: private_var := my-value

    <do something> $(private_var)




[Makefile]

$(shell echo hello) # <--- (*A)

all:

    echo all

above make file gives following results.

     

Makefile:1: *** missing separator.  Stop.

error is issued.

But, we can resolve this error by changing line (*A) into below line.

dummy := $(shell echo hello)

or

$(shell echo)


What is root cause of this?

Let's see source code of 'func_shell' function in GNUMake-v4.0.


func_shell_base(...)

{

    ...

      child_execute_job (FD_STDIN, pipedes[1], errfd, command_argv, envp); <= stdout을 pipe로 redirecdtion

    ...

          EINTRLOOP (cc, read (pipedes[0], &buffer[i], maxlen - i)); <= pipe를 읽음

    ...

          /* The child finished normally.  Replace all newlines in its output

             with spaces, and put that in the variable output buffer.  */

          fold_newlines (buffer, &i, trim_newlines);

          o = variable_buffer_output (o, buffer, i);    <= buffer값(pipe값 -> child의 stdout값)을 variable_buffer에 저장. 

    ...

}


That is, 'shell' function stores it's result(stdout) into variable_buffer.

variable_buffer is buffer that is used to store result of variable-expandsion.

What does this mean?

See below samples.


[Makefile]

$(shell echo hello) <= (*1)


[Makefile]

temp:=hello

$(temp) <= (*2)


Above analysis means, (*1) and (*2) is same expression in gnumake's point of view.


Actually, above rules are same on all other functions in gnumake.

But, most function gives empty string - that is, it doesn't write string value to stdout.

So, it doesn't write anything into variable_buffer. That's reason why above issues are not shown at other functions.

And, in gnumake's point of view, 'function' and 'variable' is same in concept.

So, we can say that 'function' is just special variable that can get arguments - a kind of 'predefined variable'.

Therefore, we should use same concept for 'variable' and 'function' to understand gnumake.


[[ Tested on GCC 4.x ]]


static void

test01_00(char buf[TEST01_SZ]) {

        printf("In func: %lu\n", sizeof(buf));

}


static void

test01(void) {

        char buf[TEST01_SZ];

        printf("In local: %lu\n", sizeof(buf));

        test01_00(buf);

}


int

main(int argc, const char *argv[]) {

        test01();

        return 0;

}


<< Run on 64bit linux >>

$ ./a.out

In local: 16   <== array size

In func: 8     <== pointer size


'sizeof(<array>)' works as expected in the scope where it is declared. But, array that is declared as parameter, is regarded same as 'pointer' (as you can see above).

I don't think this is defined in C specification.

So, I think it is just one of special characteristics of GCC.

Is it interesting,isn't it?


즉 array를 선언한 곳에서 sizeof(<array>)는 기대한 대로 동작하는데, argument로 넘긴 array의 경우, pointer와 동일하게 취급하는 것을 알 수 있다.

개인적인 생각으로, 이것이, C spec 사항일 것 같지는 않다.

그러니, 그냥 GCC 특성 중 하나라고 생각한다....

꽤나 재미있는 내용이라, 일단 적어 둔다. ^_^

'Language > C&C++' 카테고리의 다른 글

[C/C++] Small tip - Argument passed to main() function.  (0) 2015.10.19
[C/C++] Template with value  (0) 2015.04.28
[Macro] '##' macro operator  (0) 2015.02.11
'malloc' and 'Segmentation fault'  (0) 2014.07.25
[GCC] Initialize array...  (0) 2014.07.25

ext4에서 'readdir' system call을 이용해서 directory를 읽으면, directory가 가지는 file list를 읽을 수 있다.

이때, 읽히는 file의 순서는 어떻게 정해지는가?

경험적으로 대부분의 개발자라면, 이 순서가 일정하지 않다는 것을 알고 있을 것이다.

이 부분에 대해서 좀더 깊게 살펴보기도 하자.


linux kernel에서 ext4 관련 부분의 코드를 분석해 보면 아래의 사실들을 알아 낼 수 있다.

- ext4는 directory내에서 file search등을 빠르게 하기 위해서 htree(hash tree)를 이용한다.

- 이때 사용되는 hash값의 order가 곧 readdir 로 읽어들이는 file의 order이다.

- hash 값은 'HashFunc(<hash algorithm>, <hash seed>, <file name>)"을 통해서 구한다.

- hash algorithm과 hash seed의 경우, super block에 적힌 값을 사용하면, 없는 경우 default 값을 사용한다.

- directory내의 각 file의 hash값은 directory file의 'f_pos'값 (file position값)으로 사용된다.


즉, super block에 적힌 hash algorithm과 hash seed에 따라서, directory가 같은 이름들의 file을 가지고 있다고 하더라도, readdir이 읽어 오는 file의 순서가 달라진다는 말이다.


실제로 이를 확인하기 위해서는 'dumpe2fs' 와 'debugfs' tool을 사용할 수 있다.


Assumption : '/' 가 '/dev/sda1' 에 mount 되어 있음.


>$ sudo dumpe2fs -h /dev/sda1
...
Default directory hash:   half_md4     <== hash algorithm
Directory Hash Seed:      5841608b-14fe-405e-8d28-76236cc8c496  <== seed (UUID format)
...


이후 아래와 같은 방법으로 각 file name에 해당하는 hash 값을 알 수 있다.

>$ sudo debugfs /dev/sda1
dx_hash -h half_md4 -s c773a461-6150-4fe8-abe8-96acc6086d7e vmlinuz
Hash of /vmlinuz is 0xf097024e (minor 0x0)
추가적으로 'ls -f' 를 사용하면, sorting되지 않은 순서로 file list를 읽을 수 있는데, 이 순서가 바로, hash 값에 따른 순서 즉 readdir로 읽어 들이는 순서이다.
이를 확인하기 위해서는
>$ rm dxhashs; for e in $(ls -f /); do echo "dx_hash -h half_md4 -s 5841608b-14fe-405e-8d28-76236cc8c496 $e" >> dxhashs; done; sudo debugfs -f dxhashs /dev/sda1 | grep "Hash of" | cut -d' ' -f5

와 같이 하면, 정확하게 ascending order로 출력되는 hash 값을 볼 수 있다.
즉 'ls -f' 를 통해서 보여지는 file name의 순서가 hash값이 ascending order라는 뜻이고, hash 값이 ascending order에 따라 읽어 들이는 file name의 순서가 'readdir'로 읽어 들이는 순서와 일치한다.

Hash Seed를 바꾸는 방법은 아래와 같다.(간단한데, 나중에 분명히 잊어 먹을테니...)
/dev/sda2 가 /opt에 mount되어 있고, ext4 file system인 경우.

># unmount /opt
># debugfs -w /dev/sda2
>debugfs: ssv hash_seed ceeba115-4091-4b79-aac9-36ccee4a5349  <= UUID of hash seed to change


+ Recent posts