템플릿 얘기가 좀 길어졌는데.. 다음 주제는 가상 함수와 관련된 엉뚱한 생각이다.
C++이라는 언어는 앞서 잠깐 언급했던 바와 같이, '오버로딩과 오버라이딩' 사이에 뭔가 견제를 하는 게 있다.
그것과 비슷하달까, C++은 오버라이딩과 멤버 함수 포인터 사이에도 디자인 차원에서 선을 긋는 게 있어 보인다.

무슨 말이냐 하면.. 가상 함수가 존재하는 어떤 객체가 주어졌을 때, 이 객체가 참조하는 vtable 값을 직접 확인하는 것을 언어 차원에서 전혀 허용하거나 지원· 고려하지 않는다.
이 객체의 어떤 가상함수는 부모 클래스의 것과 같은지, 아니면 오버라이딩 됐는지.. 이런 것을 알 수 없다.

&obj->Foo == &TBase::Foo 이런 식으로 비교하는 거?? 가상과 비가상 불문하고 다 안 된다. 클래스의 non-static 멤버 함수의 주소를 얻는 건 컴파일 타임 바인딩이 가능한 &클래스::멤버 형태만 허용될 뿐, 런타임 바인딩인 &변수->멤버는 안 된다. 그냥 컴파일 에러로 처리된다.

멤버 함수 포인터를 이용해서 pFunc에다가 특정 클래스의 Foo를 집어넣었더라도.. (obj->*pFunc)()를 호출해 보면 obj->Foo()를 호출한 것과 동일하게 접수된다.
멤버 함수 포인터에는 자신의 vtable을 참조해서 그걸 호출해 주는 thunk 함수만 연결되기 때문이다. 그 아래의 vtable 상의 주소로 다이렉트 접근이나 제어는 안 된다! 신기하지 않은가?
(하긴, 다중 상속 체계에서는 this 오프셋 보정도 이런 thunk가 하는 일 중 하나이겠지만)

C++ 클래스에서 vtable이란 걸 바이너리 차원에서 꼭 이렇게 구현해야 한다고 C++ 표준에 규정돼 있지는 않다.
그러나 실제로 구현되는 방식은 컴파일러 불문하고 거의 뻔할 뻔자이다. 그렇기 때문에 다음과 같은 동작을 억지로 구현해 줄 수 있다.

class Base {
public:
  int x;
  virtual void VF1() {}
  virtual void VF2() {}

  void* GetFuncPtr(int n) { //0: VF1, 1: VF2
    void*** pppf = (void***)this;
    return (*pppf)[n];
  }
};

무려 삼중 포인터가 쓰인 GetFuncPtr이라는 저 함수를 주목하시라. void에 대한 포인터(1)의 배열(2)을 가리키는 포인터(3)이기 때문에 삼중이 된 것이다.
가상 함수가 들어있는 클래스는 맨 첫 멤버가 vtable 포인터이기 때문에 this에 대해 저런 형변환이 가능하다.

그 다음으로, Base에 대해 1번 함수를 오버라이드한 Derived1, 2번 함수를 오버라이드한 Derived2, 그리고 둘 다 오버라이드한 Derived3. 이 세 클래스를 다음과 같이 선언해 보자.

class Derived1 : public Base {
public:
  int y;
  virtual void VF1() {}
};

class Derived2 : public Base {
public:
  int z;
  virtual void VF2() {}
};

class Derived3 : public Base {
public:
  int w;
  virtual void VF1() {}
  virtual void VF2() {}
};

요렇게 한 뒤, Base, Derived1, Derived2, Derived3 아무 객체나 선언해서 GetFuncPtr(0)을 호출해 보면 Base와 Derived2는 같은 값을 되돌린다. Derived2는 VF1을 오버라이드 하지 않았기 때문이다.
GetFuncPtr(1)을 호출해 보면 Derived1이 Base의 것과 같은 값을 되돌린다. 이유는 동일.

그렇다고 이 주소값은 &Base::VF1, &Derived2::VF2 처럼 실존하는 멤버 함수의 주소를 C++ 연산자를 통해서 얻은 주소값과 일치하지 않는다. 이거는 멤버 함수 포인터에다가 대입해서 호출을 할 수 있지 않다.
애초에 멤버 함수 포인터는 일반 함수 포인터와 달리 임의의 정수형으로부터 대입하는 게 아예 불가능하지 싶다. reinterpret_cast나 C-style cast도 되지 않는다.
그러니 저 값은 그냥 클래스 간에 값이 같은지 다른지 비교 용도로만 써먹을 수 있을 뿐이다.

그리고 사실은 언제나 저렇게 가상 함수 2개짜리 vtable이 생긴다는 보장도 없다.
클래스를 만들어서 멤버 함수를 virtual로 선언했지만 실제 코드에서 이 클래스의 인스턴스를 한 번도 포인터로 접근하지 않아서 런타임(다이나믹) 바인딩이 필요하지가 않다면..
컴파일러가 최적화 스킬을 발휘해서 vtable을 곧이곧대로 만들지 않을 수도 있다. 그러면 우리가 저렇게 만든 GetFuncPtr 함수도 제대로 맞게 동작하지 않을 것이다.

일반적인 함수라면.. 형태가 너무 단순해서 컴파일러가 평소에는 인라이닝이나 인트린식으로 실컷 최적화한다고 하더라도 그 함수의 주소가 필요할 때는 일반적인 함수 포인터 값이 반드시 제공돼야 한다. 최적화는 부가 기능일 뿐이지만, 함수 포인터는 언어 스펙에서 제공되는 필수 기능이기 때문이다.
그러나 vtable에 명시된 함수 주소는 그렇게 공식적으로 제공되는 기능이 아니다. 프로그래머가 언제까지나 꼼수로.. at your own risk를 염두에 두고 써야 한다.

이상이다.
글을 맺으면서 문득 드는 생각인데.. C++의 RTTI (런타임 type info)가 내부적으로 구현되는 방식도 가상 함수가 구현되는 방식과 아주 밀접한 관계가 있으며, 서로 떼어서 생각할 수 없어 보인다.

vtable이라는 게 결국은 한 클래스와 무조건 일대일 대응하는 고유한 정보이니, 참조하는 vtable이 동일한 객체는 동일한 클래스의 인스턴스임이 보장되기 때문이다.
그러니 자기 클래스의 이름이라든가, 부모 클래스 목록 같은 RTTI도 vtable과 함께 두거나 최소한 RTTI를 가리키는 포인터를 vtable에다가 둘 법도 해 보인다. dynamic_cast 연산자는 그런 정보를 참조하면서 동작하면 될 테고.

아 그런데.. 이런 깔끔한 관계는 단일 상속 체계에서나 보장되겠다;;
다중 상속이라면 2개째 이후의 기반(부모) 클래스에 대해서 매번 vtable 포인터가 또 추가될 테니 일이 정말 복잡해지겠다.
단일 상속에서는 복잡도가 뭔가 1씩 더해지는 것만 생각하면 됐는데, 다중 상속에서는 2씩 곱해지는 수준으로 복잡도가 폭발적으로 증가한다.;;

그러니 C++ 표준화 이전부터 RTTI를 자체 구현했던 C++ 프레임워크들은(가령, MFC 같은 골동품) 그런 건 깔끔하게 포기하고 단일 상속만 염두에 두고 저런 기능들을 구현했지 싶다.

말이 길어져 버렸는데.. 암튼 이 글의 결론은
"이 ptr은 Base의 파생 클래스이긴 한데요, 특정 무슨 함수가 오버라이드 돼서 Base의 원래 것과는 달라졌는지 아닌지만 좀 알 수 있을까요?"
요건 언어 차원에서 알려주지 않는다는 것이다. 그런 건 query하는 함수를 사용자가 수동으로 구현해 줘야 한다.

Posted by 사무엘

2025/04/17 08:35 2025/04/17 08:35
,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/2374

객체지향 언어라면 파생 클래스 코드에서 기반 클래스의 멤버에 접근하는 것이야 너무 당연히 가능하다. 상속을 protected나 public으로 받기만 했다면 말이다.
기반 클래스의 scope에서 선언된 내부 클래스라 하더라도 마음껏 인스턴스를 만들고 참조할 수 있다.

class Base {
protected:
  int x;
  class InnerClass {
  public:
    int z;
  };
};

class Derived: public Base {
public:
  void Foo() {
    x = 100; //base 꺼
    InnerClass o; o.z = 100; //base 꺼
  }
};

그런데 이게 평범한 클래스가 아니라 ‘템플릿 클래스’라면 상황이 어찌 될까?

template<typename T>
class TBase {
protected:
  int x;
  class InnerClass {
  public:
    int z;
  };
};

template<typename T>
class TDerived : public TBase<T> {
public:
  void Foo() {
    x = 100;
    InnerClass o; o.z = 100;
  }
};

한동안은 이 코드는 잘 통용됐다. 문법적으로 아무 하자가 없어 보이지 않는가? 내가 아는 한 Visual C++ 2010 컴파일러 정도까지는 이상이 없었다.
그런데 2010년대에 C++ 표준이 바뀌었는지.. 저건 후대의 버전부터는 컴파일되지 않고 오류를 일으키기 시작했다.
2019/2022에서 말이다. 그 사이에(2012, 13, 15, 17??) 정확히 언제부터 변화가 생겼는지는 잘 모르겠다.

TDerived에서 기반 클래스 멤버인 x에 접근하는 것 자체가 언어 차원에서 봉쇄되고 금지된 건 아니다. 단지, 템플릿 클래스에서 코드의 의미 명료화를 위해 좀 깐깐한 조치가 취해진 것 같다.

TDerived에서 x에다 접근하려면
this->x 라고 일일이 쓰든가, 아니면 TBase<T>::x라고 타입을 명시해 줘야 한다. 그 x를 지칭하기 위해서 어쨌든 x 단독으로 방치만 하지 않으면 된다.

CInternalClass도 주변에다 좀 decoration을 해 줘야 한다. 얘는 멤버 변수나 함수가 아니라 타입이다 보니.. typename 내지 using 처방을 해 주면 된다.
참조할 때 매번 typename TBase<T>::InnerClass 이러던가..
아니면 TDerived의 선언부에다가 using InnerClass = TBase<T>::InnerClass 이렇게 박아 주고 나서 다음부터 InnerClass만 쓰면 된다.

흐음~~
내가 기억하기로 C++에서 템플릿이라는 건 처음 도입됐던 시절엔 거의 C++판 매크로나 마찬가지였다.
템플릿 인자가 무엇이 들어오느냐에 따라 코드의 의미가 귀에 걸면 귀걸이, 코에 걸면 코걸이(특히 값이냐 타입이냐)처럼 완전히 달라질 수 있었기 때문이다.

T가 int냐, double이냐, 1000바이트 짜리 뚱뚱한 클래스냐~ 이거는 컴파일러 입장에서 단순히 코드 생성이나 최적화 전략만을 좌우할 것이다.
그러나 T::member 이런 거는..? T에 어떤 클래스가 오느냐에 따라서 member는 멤버 변수? 멤버 함수? 심지어 내부의 다른 클래스/enum 이름이 될 수도 있다. 멤버라 하더라도 static 멤버가 될 수 있고, non-static이 될 수도 있다!

이러니 템플릿 코드는 그거 단독으로 문법 체크를 하는 게 매우 난감했다.
템플릿에다가는 그 어떤 아무말을 씨부려 놔도 그 자체는 컴파일러가 전혀 개의치 않고 넘어갔다. #define MACRO(x) 그 다음에 그 어떤 아무말을 씨부리건 이것만으로는 컴파일 에러가 발생하지 않는 것과 같은 이치이다. 에러는 이 매크로를 사용하는 곳에서 발생할 뿐..

템플릿도 TBase<int>건 TBase<POINT> 처럼 그 템플릿에다가 인자를 줘서 실제로 사용할 때에야 파싱과 코드 생성이 시작됐다. 그때에야 에러가 발생했다.

그랬는데.. 요즘 C++ 언어의 디자인 추세는 템플릿이 너무 무질서하고 자유도가 너무 높은 것을 통제하려는 것 같다.
특히 템플릿만을 단독으로도 최대한 분석한다. 템플릿 인자로 그 무엇을 넣더라도 그와 무관하게 무조건 구문 실패와 에러가 발생할 만한 것은 굳이 템플릿을 실제로 사용하지 않더라도 지적해 준다.

일례로, 위의 코드에서 TDerived::Foo() 안에다가 x=100이 아니라 y=100을 넣으면 바로 에러가 난다.
이렇게 하려다 보니 T::member라는 토큰은 템플릿 인자 T에 무엇이 들어오건 최소한 멤버인지 타입명인지 정도는 고정적으로 의미 보장이 돼야겠다. 그래서 typename T::member 같은 조치가 취해졌다.

그리고 T에 무엇이 들어오건 주변 문맥을 고정시켜야겠으니 implicit하게 부모 클래스 멤버에 접근 가능하던 혜택(?)도 없어진 것이다.
그 혜택을 다시 입고 싶으면 using을 활용해서 직접 요청을 해야 된다. using은 typedef의 상위 호환 대체제이기도 하니..

왜, 같은 이름의 함수로 오버로딩과 오버라이딩을 동시에 시도했을 때도(예: virtual void foo()와  void foo(int)를 동시 시도) 파생 클래스에서는 오버라이딩 되지 않은 부모 클래스 멤버로 자동 접근하는 건 "막힌다". 그런 것과 비슷한 느낌이 든다.
막힌 멤버로 접근하려면 역시 부모 클래스 이름을 일일이 써 주거나, using Base::foo 라고 자동 접근 요청을 해야 된다. 이 조치도.. 나중에 일부러 취해진 것이다.

C++ 템플릿은 임의의 타입들을 다 boxing해서 단일 바이너리(컴파일된 코드)만으로 모든 템플릿 인자를 담당하는 '제네릭' 같은 물건이 아니다. 하지만 그렇다고 정말 귀걸이 코걸이가 다 되는 C++판 매크로도 아니니.. 그 중간의 다른 무언가를 지향하는 것 같다.

자, 본인이 이 글을 쓰게 된 계기는..
이 홈페이지의 옛날 자료실에 있는 "정렬 알고리즘 모음집" C++ 코드가 최신 컴파일러에서는 컴파일되지 않는다는 걸 뒤늦게 발견했기 때문이다.
템플릿 클래스 사이에 상속을 구현했는데.. 이제는 코드를 한참 뜯어고쳐야 컴파일 될 것 같다. ㄲㄲㄲㄲㄲ
이 참에 옛날 자료실을 다 github로 옮기는 것도 생각해 봐야겠다.

Posted by 사무엘

2025/04/14 13:00 2025/04/14 13:00
, , ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/2373

1. 톰 아저씨의 오두막 (1852)

이 소설은 흑인 노예 제도가 있던 19세기 미국의 역사를 바꿔 놓은 엄청난 명작 화제작 문제작이었다. 작가는 '해리엇 스토(우)'라는 이름의 목사 사모이다.
주인공인 톰은 흑인 노예이지만 기독교 신앙으로 똘똘 뭉친 초 대인배 성인군자이다. 그는 처음에는 선한 백인 집안에 소속돼 있었지만, 거기 가세가 기울면서 하필 악한 백인에게 팔려 간다.

그 악한 주인도 톰이 일꾼으로서 아주 유능하다는 건 금세 알아본다. 그래서 그는 톰에게 완장을 씌워 주고는 동료 동족 노예들을 줘 패고 학대하면서 군기 잡고 관리하라고 지시한다. 그러나 톰은 이에 불응한다.
단순히 어렵고 힘든 일이나 주인의 목숨을 구하는 일이라면 자기 목숨을 걸고라도 하겠지만, 저런 반인륜적인 명령은 따를 수 없다고 말이다.

톰은 그 댓가로 자기가 악한 주인에게 직접 잔인한 폭행을 당하고 죽도록 얻어터진다. 그는 뒤늦게 찾아온 옛 주인 가족들이 보는 앞에서 기력이 다해 죽고 만다. 그런데 그는 죽어가는 와중에도 악한 주인을 미워하거나 저주하지 않고 그를 위해서도 중보 기도를 한다.

이야~ 신파가 통하던 낭만주의 시절에 이런 소설이 하나 있었으면 독자들이 다들 울고불고 했지 싶다.
뭐, 19세기 중반이면 서구 제국주의 열강 사이에서도 노예 제도는 거의 끝물을 경험하고 있었다. 아 물론 인종 차별과 식민지 착취는 당연히 있었지만, 최소한 국가 차원에서 휴먼 노예를 대놓고 경매장에다 진열하고 값을 흥정하고 사고 파는 짓거리는 없어졌다는 뜻이다.

그리고 더 중요한 거.
노예 제도를 옹호했던 미국 남부라고 해도, 실제로 흑인 노예를 거느릴 경제력이 되는 계층은 아주 소수의 농장주 지주 급 금수저들뿐이었다. 그때 성인 노예 1인은 요즘으로 치면 고급 승용차나 상용차(트럭..)에 맞먹는 비싼 가격을 자랑했기 때문이다.

남부 농촌 사람들이라고 해서 집집마다 흑인 노예 한둘씩 데리고 있었던 게 절대 아니다. 월급 주고 부리는 가정부 메이드랑 헷갈리지 마시라.
그럼에도 불구하고 노예도 없는 흙수저 주제에 남부 사람들이 노예 제도 수호를 위해 남북전쟁 때 기꺼이 참전한 이유는..
"내가 비록 직접 노예를 갖고 있지는 않지만 남의 노예라도 몽땅 해방돼 버려서 나랑 동급의 신분이 되는 건 용납할 수 없지!" 이런 심리 때문이었다고 한다. -_-;;;

암튼.. 이렇게 남북전쟁까지 촉발시킨 이 소설의 제목은 Uncle Tom's Cabin이고, 부제는 Life among the lowly (밑바닥 인생)이었다.
이게 우리나라엔 이 광수(19xx 일제 시대), 계 용묵(1954) 같은 소설가에 의해 번역되어 소개됐는데..
이때는 소설 제목이 "검둥이의 설움"....;;;;이라고 붙여졌다!! ㄷㄷㄷㄷㄷ 원제와 부제 내용을 저렇게 적당히 섞은 것 같다.

사용자 삽입 이미지

성인군자 톰이 일체의 물리적인 저항· 항쟁 없이 거의 순교자처럼 죽는 슬픈 소설을 읽고 숙연해져 있었는데 그 당사자가 한낱 검둥이래~ㅠㅠㅠ 완전 현타 온다.
지금은 한국어 검둥이도 영어 ni***와 맞먹는 인종차별 멸칭으로 전락했으니 말이다.. 거 참 '동무, 빨갱이' 이런 단어처럼 멀쩡한 단어가 어감이 타락해 버렸다.

하긴, 1950년대 동시기엔 미국에서도 담배 광고에 방긋 웃는 아기 얼굴이 들어갔고, "의사들이 가장 많이 선택한 최고의 담배!!" 이 짓거리가 행해졌다.
그리고 남편이 가장의 권위 유지(...)를 위해 필요하다면 마누라라도 볼기를 패서 훈육해야 하는지를 갖고 신문에서 독자들끼리 논쟁을 했었다. 그만큼 그때와 지금은 시대가 사람들 상식과 통념이 서로 극단적으로 달라졌다. (☞ 이전 글 )

우리나라는 지리적 배경 때문에 히틀러나 나치에 대한 반감은 서구권보다 덜하고, 인종 갈등도 미국에 비할 바가 아니다.
옛날에 김 태영 국방부 장관이 재임 중에 "아프리카는 온통 밀림 속에서 무식한 흑인들이나 뛰어다니는 곳이다"라고.. 가히 "민중은 개돼지"를 능가하는 말실수를 했던 바 있다.. 제주 해군 기지가 필요하다고 설득하던 중에 저런 말이 도대체 왜 튀어나온 걸까? =_=

그래도 저것도 그냥 해프닝으로 끝났다. 저 말 때문에 저 사람이 옷 벗고 커리어 끝장나거나, 밤에 골목길에서 칼빵 맞는다거나 하지는 않았다.
반대로 우리나라도 수십 년 전에 LA 폭동 때 궤멸적인 피해를 입었지만.. 그게 딱히 인종차별이나 흑인 혐오 정서로 이어지지도 않았다. 그냥 전반적으로 무덤덤한 거다. '흑형' 정도면 중립적이거나 오히려 긍정적인 말 아닌가? =_=

다 좋은데.. 애들 보는 멀쩡한 동화 애니메이션 주인공의 피부색은 좀 건드리지 않았으면 좋겠다. =_=;;;;;;;;; 특히 요즘 D모 사 작품들 말이다. =_=;;

2. 장발장 (1863)

아이고, 톰 아저씨 이야기가 너무 길어졌다.
톰 아저씨로부터 10년쯤 뒤에는 프랑스에서 '빅토르 위고'라는 대문호가 그 이름도 유명한 '장발장'이라는 소설을 발표했다.

'장발장'은 거기 나오는 주인공의 이름이고, 소설의 원제는 '레 미제라블' -- 비참한 사람들이다. 이거 마치 노래에 대해서 "가사 첫 줄 vs 원제"와 비슷한 관계이려나?
톰 아저씨 소설의 부제에도 the lowly 밑바닥 하층민라는 단어가 있기는 하다만, 장발장은 아예 '비참한, 가련한'이라는 단어가 있다.

이 소설을 어린 시절에 요약본 동화로만 접한 사람은 그냥 생계형 잡범 누범이 어느 성직자의 도움으로 개과천선해서 사회에 공헌하는 인재가 됐다~~ "끝" 이렇게만 생각할지 모른다. 솔직히 나도 그 정도밖에 모른다.
하지만 이거 원작은 1000 페이지를 넘는 대하드라마 급 장편 소설이랜다. 혁명기 시절의 프랑스 역사를 미주알고주알 다루면서 서민들의 비참한 삶과 동심파괴 면모들을 그대로 고발하고 폭로했다고 한다.

'장발장'... 이름으로나 캐릭터로나 머리가 더부룩한 '장발'-_-;;;인 떡대 남자가 떠오른다만..
살아 온 배경은 완전히 다르지만 이 사람하고 톰 아저씨도 접점이 완전히 없지는 않은 것 같다.

사용자 삽입 이미지

이 소설은 1910년대에 최 남선이 번역해서 국내에 처음으로 소개했다고 한다. 그 긴 원판을 몽땅 번역한 건 당연히 아니고 요약본일 텐데... 이 양반은 제목을 "너 참 불쌍타"라고 붙였다.;;;; 그래, 비참하고 가련한 사람들이니까 불쌍하지. ㅠㅠㅠㅠㅠ

3. 옛날 영화 제목의 음역· 번역

(1) The Sword And The Sorcerer (1982)

사용자 삽입 이미지

스워드 ㄷㄷㄷㄷㄷㄷㄷㄷ..!!!
뭔가 스물스물 스웜 sworm 같다. ㅋㅋㅋㅋㅋㅋㅋ

(2) The Hidden (1987)

사용자 삽입 이미지

하이든~~ 아놔ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ
본토에서는 단모음인데 우리나라에서 장모음을 쓰는 게 DirectX (다이렉트/디렉트) 같은 것도 있다만.. 하이든은 너무했다 ㅠㅠㅠㅠㅠㅠ
작중의 외계 악마가 락 음악이 아니라 서양 클래식을 좋아할 것 같다 이 말이다~~!!

사용자 삽입 이미지

하긴, 영어로 저렇게 "동사의 과거분사형"만 꽝 박아놓은 제목은 우리말로 직역이 난감하다.
그래서 Frozen은 겨울왕국이라고 좀 의역됐고, Taken은.. 걍 테이큰이라고 음역됐다.

(3) The Hitman (1991)

사용자 삽입 이미지

이건 뭐 번역도 다른 영단어로 하다니 참 난감하다.
히트맨은 암살자~~ 이런 뜻이다만, 스트롱맨은 도대체 뭐냐. ㅋㅋㅋㅋㅋ
그 와중에 "네놈을 살려 두긴 '쌀'이 아까워!!"도 백미다. 너무나 한국적으로 광고카피를 만들었다.

이렇듯, 소설이고 영화고 제목을 우리말로 꽤 창의적으로 옮긴 사례들을 나열해 보니 참 재미있고 병맛스럽다. ㅋㅋㅋㅋ

Posted by 사무엘

2025/04/02 19:35 2025/04/02 19:35
, ,
Response
No Trackback , No Comment
RSS :
http://moogi.new21.org/tc/rss/response/2372


블로그 이미지

그런즉 이제 애호박, 단호박, 늙은호박 이 셋은 항상 있으나, 그 중에 제일은 늙은호박이니라.

- 사무엘

Archives

Authors

  1. 사무엘

Calendar

«   2025/04   »
    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      

Site Stats

Total hits:
3919518
Today:
1083
Yesterday:
2591