Programming and my thoughts

책에는 몇가지 기초 문제들에 대한 해답이 나와있다.

아주 고전적인 것도 있고... 

한 번 읽고 반추의 과정으로 곰곰히 생각해봐야 온전히 내 것으로 만들어 숙지할 수 있을 것 같다.


1. 네임 맹글링 (Name Mangling) 이란 ?


우선, 컴파일 과정을 살펴보자.


전처리기 - 컴파일 - 링킹


c++ 컴파일은 위와 같은 과정을 거쳐 최종적으로 .exe 라는 파일 형태가 된다.

c++ 컴파일러의 경우 컴파일 과정중 링킹(Linking)에서 링커가 맹글링을 이용해 함수의 이름, 변수의 이름 같은 것들의 유일한 이름(심볼)을 지정하는 과정이 있다.

int plus(int x, int y)
int plus(int x, int y, int z)

위 plus 함수는 함수 오버로딩의 한 예다.

c++ 에서 이 코드를 컴파일 하면... 링킹과정에서...

첫번째 plus 는 _2plus@8 와 같은 심볼이 만들어지고...

두번째 plus 는 _3plus@12 와 같은 심볼이 만들어지고 실제 호출시 여기를 바라보게 된다.


이것을 맹글링이라고 하는데 오버로딩된 함수의 경우 이 맹글링 과정을 거쳐 유일한 심볼명이 만들어진다고 한다.


2. C++ 과 Java 의 닮은점과 차이점은 ?


정말... 아주 고전적인 주제다.

한 번쯤 생각해볼만은 한데... 돌발적으로 이런 질문을 받으면 "포인터" 외에는 특별히 생각나는게 없었다.

그만큼 이 주제에 대해 아무 생각이 없었다는 사실에 대한 반증이다 (...)


책에서는 C++ 과 Java 를 비교했지만...

요즘에는 JavaScript, Python 같은 언어도 많이 쓰니... 이런 언어들과의 비교도 생각해볼 필요가 있겠다.


한가지 생각할 것은... 언어는 도구일 뿐이라는 것이다.

어떤 언어가 좋고 나쁘다는 것보다는 어떤 언어가 어떤 환경에서 어떤 개발 주제에 적합한 지의 차이를 아는 것이 중요하다고 생각한다.

요즘에는 플랫폼과 프레임워크도 워낙 세분화되어 있어... 언어 하나만 가지고는 중요한 판단을 내리기는 어렵다고 생각한다.


예를 들어...

MEAN 개발방법은... (M = MongoDB, E = ExpressJS, A = AngularJS, N = Node.JS) 의 약어인데...

이것을 두루 사용하여 개발하는 방법을 말한다.


요즘에는 그래서 폴리글랏 프로그래밍이라는 말도 있지않은가.


새로운 프로젝트를 앞에 두고 언어, 플랫폼, 프레임워크를 포함하여 생각할 수 있는 안목을 기르자.

언어는 도구일 뿐이라는 시각에서 보면...

새로운 언어, 플랫폼, 프레임워크 역시 내가 안 써본 도구일뿐... 모르면 배워서 쓰면되는 것이다.


> Java 는 전처리기가 없다.

여기서 쏟아져나오는 사례가 많다. typedef, enum, defined 같은 키워드가 없는 것이다.


> Java 는 클래스는 있지만, 구조체(struct), 공용체(union)는 없다.


> Java 에서 선언하는 모든 함수는 클래스 선언안에 있어야 한다.


> Java 에는 Interface 라는 개념이 있다. C++ 에는 이것이 없으며, 대신 C++ 은 ABS (Abstract Base Class) 를 사용하여 Java 의 Interface 역할을 하도록 한다. ABS 는 추상클래스를 말한다. 태어난 시기로 보면 C++ 의 ABS 가 먼저 나온 것이다.


> Java 는 클래스 다중상속이 없으나, Interface 를 이용해서 다중상속을 구현할 수 있다.


> Java 의 String 형의 객체는 Immutable 의 특성을 가진다.

이것도 상당히 중요한 개념인데... 한 번쯤 생각해볼 필요가 있다.

String str1 = "Hello";
String str2 = str1;
System.out.println("str1.hashcode() = " + str1.hashCode());
System.out.println("str2.hashcode() = " + str2.hashCode());
System.out.println(str1 + " " + str2);

str2 = "World";
System.out.println(str1 + " " + str2);
System.out.println("str1.hashcode() = " + str1.hashCode());
System.out.println("str2.hashcode() = " + str2.hashCode());
str1.hashcode() = 69609650
str2.hashcode() = 69609650
Hello Hello
Hello World
str1.hashcode() = 69609650
str2.hashcode() = 83766130

이 코드를 잘 보자.

str1 과 str2 가 모두 "Hello" 를 가리키고 있었는데... HashCode 의 값이 같았다. 

(Java 의 hashCode() 함수는 객체의 고유 주소에 대한 해시값이다)

즉... 같은 메모리를 쳐다보고 있었다.


그런데 str2 = "World" 라고 하면...

str2 이 원래 보고있던 "Hello" 라는 메모리 공간의 값을 바꾸는 것이 아니라...

"World" 라고 하는 새로운 메모리 공간을 만들어서 이것을 str2 이 가리키도록 하는 것이다.

그래서 Hello World 라고 출력된 이후에는... str1 과 str2 의 HashCode 값이 달라지는 것이다.


즉, Java 의 String 객체는 Immutable 의 특성을 가진다. (변경불가)

만약 어떤 String 객체를 만들어서 이를 Mutable 하게끔 하여 자유롭게 변경하고 싶다면...

Java 의 경우 StringBuffer 라는 클래스를 이용하면 된다.

C# 의 경우 StringBuilder 클래스를 이용한다.


어떤 함수에서 String 객체를 매번 만들어서 리턴하는데... 엄청나게 많은 Write 를 통해 이 객체를 만든다고 가정해보자.

이 경우 순수한 String 객체를 이용하는 것 보다는.... StringBuffer, StringBuilder 를 이용하여 문자열을 구성하는 것이 현명한 방법이다.

String 객체는 변경이 생길 때마다 새로운 메모리를 이용하는 것이므로... 빈번하게 수정을 할 필요가 있는 String 객체가 필요할 경우 앞서 설명한 클래스들을 활용하자.

언어마다 이런 특징은 조금씩 차이가 있다. (예 : Python 은 StringBuffer, StringBuilder 개념이 없다)

그래서 각 언어를 배울 때마다 이런 차이점을 염두에 두고 공부하고 개발하면 많은 것들을 배울 수 있다.


> Java 에는 포인터가 없다.

포인터의 개념은 분명히 필요한데... Java 는 많은 프로그래머들에게 자괴감을 안겨주는 이 개념을 잘 안보이게끔 숨겨놓은 것이다.

실제로 대부분의 컴퓨터 프로그램은 포인터 천국인 것...


Java 에는 C++ 의 * 처럼 명시적인 포인터 선언을 할 수 없다.

즉... 포인터를 이용한 주소 변경 혹은 포인터 연산이 불가능한 것이다.


C++ 에서 명시적인 포인터가 필요한 이유는 배열에 접근하기 위함, 그리고 문자열을 다루기 위함이라는 목적이 크다.

( 예 : C,C++ 에서는 char* ptr 이라고 선언하여 종료 문자열 null 로 이 문자열의 끝을 알린다 )

그러나 Java 에서는 문자열 클래스 String 을 제공하기 때문에 이 목적이 사라지게 되었다.

또한, Java 에서는 배열 자체가 객체화되어 있어 C,C++ 에서 제공하던 포인터보다 막강한 기능을 제공한다.


> Java 에는 포인터가 없으므로... C++ 의 스코프 접근 연산자(::)가 없고... 마찬가지로 포인터 접근 연산자(->) 역시 없다.


> Java 는 C++ 보다는 상대적으로 엄격한 타입 검사를 수행한다.

이 개념도 상당히 중요한데... C++ 에 있던 포인터가 Java 에 없으므로 이것을 출발점으로 하여 사유를 하다보면 자연스러운 것이다.

공부하다보면... C, C++ 로 프로그램을 만들더라도 Java 처럼 만들라는 말도 들어본 적이 있을 것이다.

타입검사를 엄격하게 수행하는 것이 장기적인 문제 발생을 줄여준다.


> Java 에서 모든 객체(인스턴스)는 이를 전달시 참조에 의한 전달을 이용한다. (Call By Reference)

이건 앞서 포스팅에서도 언급하였다.

객체를 함수에 넣을 때에 새로운 객체가 생성되는 것이 아니라... 동일한 객체를 참조하게 된다.


> Java 에는 virtual 키워드가 없다.

C++ 에는 virtual 이라는 키워드를 이용하여 동적 바인딩을 수행하나... Java 는 기본적으로 동적 바인딩을 수행한다.


여기까지이다...


3. 얕은 복사(Shallow Copy)와 깊은 복사(Deep Copy)의 차이점은?


이것도 고전중의 고전이다.

예를들어 클래스 A 의 인스턴스 a 가 있는데... 이것을 b 에 복사한다고 가정해보자.

A a = new A();
A a2 = new A();
a2 = a;

// 혹은
A a = new A();
A a2 = new A(a);

단순한 코드이지만... 많은 의미를 내포하고 있다.


클래스 A 에 시스템 정의 자료형(Primitive Data Type)밖에 없다면 모든 멤버 필드에서 깊은 복사가 발생한다.

그러나 클래스 A 에 사용자 정의 자료형(User Defined Data Type)이 있다면... 이 필드는 얕은 복사가 발생한다.


그런데 얕은건 뭐고 깊은건 뭔가...


예를들어 클래스 A 에 Pet 이라는 객체가 있었다면...

위 코드 수행 후...

a.Pet 과 a2.Pet 은 둘 다 같은 메모리 공간을 가리키고 있다.

그래서 a.Pet 을 변경하면... a2.Pet 도 변경된다.

이렇게 복사가 되어있는 상태를 얕은 복사라고 한다.


이것이 객체 복사의 기본 동작 원리이다.

기본적으로는 객체(인스턴스)에 대해서는 얕은 복사가 발생하는 것이다.

복사생성자(A a2 = new A(a)) 라든지 할당 연산자(=)를 정의하지 않으면 얕은 복사가 발생한다.


깊은 복사를 하게되면...

a2 와 a 는 서로 독립적인 객체가 된다.

a 를 바꾸면 a 에서만 변경점이 적용되는 것이다.


현업에서 개발을 하다보면 두 객체가 서로 동일한지 여부를 판단하는 코드를 작성하는 경우가 간혹 있는데... (Equal ?)

이 때에 객체가 항상 깊은 복사를 한다고 가정하고 두 객체가 동일한지 판단하도록 하는 편이다.




어서 알고리즘 챕터로 넘어가야 하는데...

블로그에 공부한 내용을 적다보니 진도가 너무 안나는 것 같다 ;;