는 inc a 형태(즉, a를 증가하라)의 어셈블리 코드로 변환됩니다. 그 이유는 주소를 얻고 원래의 타입으로 캐스팅하고 다시 값으로 돌리면 volatile 이나 const 형태의 제한자가 없어지기 때문입니다. 따라서 다수의 CPU가 있는 머신상에서 비원자적으로 a를 증가시키는 명령인 inc a를 쓰는것입니다.
하지만, 첫줄에 전제를 한바와 같이 'GCC 컴파일러 상에서 이렇단 것입니다.' 사실 C 표준은 (*(unsigned long *)&a)++ 라는 수식은 undefined 입니다.
반면에,
a++
는 a를 레지스터로 로드, 레지스터상에서 a를 inc, 레지스터에서 메모리로 a를 write하는 3단계로 구현됩니다. CPU가 여러개 달린 경우에는, inc a 는 원자적이지 않습니다. 따라서 보통은 load, increase, write 해야하는 a++ 을 사용해야 원자적인 a의 1증가가 이루어집니다.
그러나 단일 CPU 라면 inc 를 써도 원자적으로 처리가 됩니다. 추측이지만, 아마 보시고 계신 코드에서는 단일 CPU 상에서 돌아가는 코드이거나, 또는 원자적으로 처리할 필요가 없었고, 이러한 이유로 (*(unsigned long &)a)++을 했을겁니다..
일단 (*( unsigned long * )&a)++ 과 a++이 같은건 너무나 당연한겁니다.
숨겨진 의도에 대해서는 토끼님이 링크하신 글을 보시면 되리라 생각되네요. 그리고 google 님께 물어보면 참 많은 글들을 찾을 수 있는데, 결론적으로는 volatile로 선언된 변수에서 volatile을 떼어내는 code hack이라는 것입니다.
volatile 이라는건 <b>단일 CPU</b>상에서 읽기와 쓰기 동작 (여기에서 읽기와 쓰기라고 하는건 load a, store a와 같은 걸 말합니다. 따라서 b=a 라던가 a=b 가 이에 해당하죠. 하지만 a++이나 a=a+1은 제외)에 대해서 원자적으로 수행하기 위해서 사용됩니다.
volatile에 대해서 부연하자면, volatile을 사용하면 항상 메모리에 쓴다 - 즉 레지스터에 캐싱하지 않는다 - 라고 생각하시는 분들이 계신데 이건 오류입니다. volatile은 volatile 로 선언된 변수들을 컴파일러상에서 reordering하지 않는 코드를 생성합니다. 그러나 reordering은 컴파일러만 하는 것이 아니며, MMU 역시 할 수 있습니다. 따라서 반드시 명시적으로 락을 획득하거나, 메모리 베리어를 넣어주지 않으면 volatile을 쓴다고 해도 다수의 CPU가 있는 머신상에서 다수의 CPU의 관점에서 볼 때 항상 메모리에 저장된다는 보장이 없습니다. 그리고 애초부터 CPU가 한개라면 메모리에 쓰던말던 - 즉 레지스터에 캐싱되건 말건 - 아무런 상관이 없을 것이구요.
그럼 왜 이런 volatile을 썼을까.. 에 대해서는 저는 잘 모르겠네요. 커널 전문가가 아니라서.. 아래에 제가 이전에 kldp 에 포스팅 한 글을 다시 붙입니다.
Quote:
volatile은 일반적으로 portable한 코드를 위한 동기화 construct로서는
사용이 불가능합니다. 특히나 CPU가 2개 이상일 경우에는 예기치 못한 동작을 할 수 있고요...
첫번째 인용문은 IBM 의 아키텍트이고 두번째 인용문은 HP의 Tru64 UNIX & VMS Thread Architect 의 글입니다.
"....
> - when the 'volatile' keyword must be used in
> multithreaded programming?
Never in PORTABLE threaded programs. The semantics of the C and
C++ "volatile" keyword are too loose, and insufficient, to have
any particular value with threads. You don't need it if you're
using portable synchronization (like a POSIX mutex or semaphore)
because the semantics of the synchronization object provide the
consistency you need between threads.
The only use for "volatile" is in certain non-portable
"optimizations" to synchronize at (possibly) lower cost in
certain specialized circumstances. That depends on knowing and
understanding the specific semantics of "volatile" under your
particular compiler, and what other machine-specific steps you
might need to take. (For example, using "memory barrier"
builtins or assembly code.)
In general, you're best sticking with POSIX synchronization, in
which case you've got no use at all for "volatile". That is,
unless you have some existing use for the feature having nothing
to do with threads, such as to allow access to a variable after
longjmp(), or in an asynchronous signal handler, or when
accessing hardware device registers.
---
The C language defines a series of "sequence points" in the "abstract
language model" at which variable values must be consistent with language
rules. An optimizer is allowed substantial leeway in reordering or
eliminating sequence points to minimize loads and stores or other
computation. EXCEPT that operations involving a "volatile" variable must
conform to the sequence points defined in the abstract model: there is no
leeway for optimization or other modifications. Thus, all changes
previously made must be visible at each sequence point, and no subsequent
modifications may be visible at that point. (In other words, as C99 points
out explicitly, if a compiler exactly implements the language abstract
semantics at all sequence points then "volatile" is redundant.)
On a multiprocessor (which C does not recognize), "sequence points" can only
be reasonably interpreted to refer to the view of memory from that
particular processor. (Otherwise the abstract model becomes too expensive
to be useful.) Therefore, volatile may say nothing at all about the
interaction between two threads running in parallel on a multiprocessor.
On a high-performance modern SMP system, memory transactions are effectively
pipelined. A memory barrier does not "flush to memory", but rather inserts
barriers against reordering of operations in the memory pipeline. For this
to have any meaning across processors there must be a critical sequence on
EACH end of a transaction that's protected by appropriate memory barriers.
This protocol has no possible meaning for an isolated volatile variable,
and therefore cannot be applied.
The protocol can only be employed to protect the relationship between two
items; e.g., "if I assert this flag then this data has been written" paired
with "if I can see the flag is asserted, then I know the data is valid".
That's how a mutex works. The mutex is a "flag" with builtin barriers
designed to enforce the visibility (and exclusion) contract with data
manipulations that occur while holding the mutex. Making the data volatile
contributes nothing to this protocol, but inhibits possibly valuable
compiler optimizations within the code that holds the mutex, reducing
program efficiency to no (positive) end.
If you have a way to generate inline barriers (or on a machine that doesn't
require barriers), and you wish to build your own low-level protocol that
doesn't rely on synchronization (e.g., a mutex), then your compiler might
require that you use volatile -- but this is unspecified by either ANSI C
or POSIX. (That is, ANSI C doesn't recognize parallelism and therefore
doesn't apply, while POSIX applies no specific additional semantics to
"volatile".) So IF you need volatile, your code is inherently nonportable.
A corollary is that if you wish to write portable code, you have no need for
volatile. (Or at least, if you think you do have a need, it won't help you
any.)
In your case, trying to share (for unsynchronized read) a "volatile"
counter... OK. Fine. The use of volatile, portably, doesn't help; but as
long as you're not doing anything but "ticking" the counter, (not a lot of
room for optimization) it probably won't hurt. IF your variable is of a
size and alignment that the hardware can modify atomically, and IF the
compiler chooses the right instructions (this may be more likely with
volatile, statistically, but again is by no means required by any
standard), then the worst that can happen is that you'll read a stale
value. (Potentially an extremely stale value, unless there's some
synchronization that ensures memory visibility between the threads at some
regular interval.) If the above conditions are NOT true, then you may read
"corrupted" values through word tearing and related effects.
If that's acceptable, you're probably OK... but volatile isn't helping you.
이 글이 충분한 설명이 되리라 생각합니다.
Scott Meyers 와 Andrei Alexandrescu가 쓴 글인데, 두 사람다 C/C++에서는 충분히 알려져있는 사람들이라 읽을 가치가 있을 것입니다. 특히 최근 자바의 concurrency에서는 매우 유명한 Doug Lea 등이 review 한 글입니다.
이런 뜻인가요?
(void *) ((unsigned long) a++);
아니면 이런 뜻인가요?
(type) (((unsigned long) a)++)
&a : a의 주소를(unsigned long *)&
&a : a의 주소를
(unsigned long *)&a : long형 포인터로 바꾸고
*(unsigned long *)&a : 그 내용을
(*( unsigned long * )&a)++ : 1 증가시킨다
이렇게 해석하면 될 듯 싶습니다.
(*( unsigned long * )&a)++ (unsi
(*( unsigned long * )&a)++
(unsigned long *)&a => a의 주소를 unsigned long *형으로 형변환 한다
*(형변환 결과) => 형변환 결과(주소값)를 찿아가서 값을 참조한다
(참조한 값)++ => 참조한 값을 1증가 시킨다
종종 자신을 돌아보아요!~
하루 1% 릴리즈~~
a라는 변수가 volatile 일 때 GCC 컴파일러상에서[cod
a라는 변수가 volatile 일 때 GCC 컴파일러상에서
(*( unsigned long * )&a)++
는 inc a 형태(즉, a를 증가하라)의 어셈블리 코드로 변환됩니다. 그 이유는 주소를 얻고 원래의 타입으로 캐스팅하고 다시 값으로 돌리면 volatile 이나 const 형태의 제한자가 없어지기 때문입니다. 따라서 다수의 CPU가 있는 머신상에서 비원자적으로 a를 증가시키는 명령인 inc a를 쓰는것입니다.
하지만, 첫줄에 전제를 한바와 같이 'GCC 컴파일러 상에서 이렇단 것입니다.' 사실 C 표준은 (*(unsigned long *)&a)++ 라는 수식은 undefined 입니다.
반면에,
a++
는 a를 레지스터로 로드, 레지스터상에서 a를 inc, 레지스터에서 메모리로 a를 write하는 3단계로 구현됩니다. CPU가 여러개 달린 경우에는, inc a 는 원자적이지 않습니다. 따라서 보통은 load, increase, write 해야하는 a++ 을 사용해야 원자적인 a의 1증가가 이루어집니다.
그러나 단일 CPU 라면 inc 를 써도 원자적으로 처리가 됩니다. 추측이지만, 아마 보시고 계신 코드에서는 단일 CPU 상에서 돌아가는 코드이거나, 또는 원자적으로 처리할 필요가 없었고, 이러한 이유로 (*(unsigned long &)a)++을 했을겁니다..
결국 GCC에 dependent 한 코드 최적화기법입니다.
--
Passion is like genius; a miracle.
여기에 대해서는 명예의 전당에 올라 왔던(!) [url=http://bb
여기에 대해서는 명예의 전당에 올라 왔던(!) 이 글도 참고하세요.
- 토끼군
Re: (*( unsigned long * )&a)++
unsigned long *t1;
unsigned long t2;
t1 = (unsigned long *)&a;
t2 = (*t1);
t2++;
이렇게 해석 되는것 같네요
언제나 시작
(*( unsigned long * )&a)++ 코드 최적
(*( unsigned long * )&a)++
코드 최적화?
혹시, implicit conversion 을 막기 위한 것은 아닐까요?
아니면 연산자 오버로딩 같은 것 때문일지도.
a 의 type 이 무엇인가요?
[quote]여기에 대해서는 명예의 전당에 올라 왔던(!) 이 글도
일단 (*( unsigned long * )&a)++ 과 a++이 같은건 너무나 당연한겁니다.
숨겨진 의도에 대해서는 토끼님이 링크하신 글을 보시면 되리라 생각되네요. 그리고 google 님께 물어보면 참 많은 글들을 찾을 수 있는데, 결론적으로는 volatile로 선언된 변수에서 volatile을 떼어내는 code hack이라는 것입니다.
volatile 이라는건 <b>단일 CPU</b>상에서 읽기와 쓰기 동작 (여기에서 읽기와 쓰기라고 하는건 load a, store a와 같은 걸 말합니다. 따라서 b=a 라던가 a=b 가 이에 해당하죠. 하지만 a++이나 a=a+1은 제외)에 대해서 원자적으로 수행하기 위해서 사용됩니다.
volatile에 대해서 부연하자면, volatile을 사용하면 항상 메모리에 쓴다 - 즉 레지스터에 캐싱하지 않는다 - 라고 생각하시는 분들이 계신데 이건 오류입니다. volatile은 volatile 로 선언된 변수들을 컴파일러상에서 reordering하지 않는 코드를 생성합니다. 그러나 reordering은 컴파일러만 하는 것이 아니며, MMU 역시 할 수 있습니다. 따라서 반드시 명시적으로 락을 획득하거나, 메모리 베리어를 넣어주지 않으면 volatile을 쓴다고 해도 다수의 CPU가 있는 머신상에서 다수의 CPU의 관점에서 볼 때 항상 메모리에 저장된다는 보장이 없습니다. 그리고 애초부터 CPU가 한개라면 메모리에 쓰던말던 - 즉 레지스터에 캐싱되건 말건 - 아무런 상관이 없을 것이구요.
그럼 왜 이런 volatile을 썼을까.. 에 대해서는 저는 잘 모르겠네요. 커널 전문가가 아니라서.. 아래에 제가 이전에 kldp 에 포스팅 한 글을 다시 붙입니다.
--
Passion is like genius; a miracle.
[quote="pool007"]일단 (*( unsigned long
같지는 않습니다. a가 long 형이 아닐때 a++은 보통 위의 값과 달라지죠
일반적으로는 void * 형태로 받아온값이 long 형임을 확신할때
즉, 종종 함수포인터등의 인자로서 불특정인자로받은값이 특정함수내부에서는 특정값으로 사용될때 등이죠..
----------------------------------------------------------------------------
답변들 감사합니다. 제가 생각했던것보다 많은 정보를 얻었습니다.그
답변들 감사합니다.
제가 생각했던것보다 많은 정보를 얻었습니다.
그리고 공부를 좀더 해야겠다는 생각이 드네요.
댓글 달기