2016년 9월 26일 월요일

작은 시스템 설계

가장 공유가 많았었던 문맥에 맞도록 코드들과 합치고 변형해서 넣을 예정.


최근 작은 시스템 설계 및 검수 의뢰가 들어왔다. 수백억 짜리 공사인데 IT 관련 기술이 들어가야 한다고 했다. 이와 관련한 일련의 고민 과정들을 적어 보려고 한다. 자세한 것은 공개하기 힘드므로... 
Embedded + SI +  X(비공개)였다. 시스템 통합에는 DB와 원격 관리, 백업 기능은 기본 중의 기본이다.

1

시스템이 나누어져 있으면 큰 시스템이 아니고 작은 시스템이라고 보면 된다. 가장 먼저 고려되어야 할 것은 가용성이다. 학부 과정에서 배우는 고가용성이란 다음과 같다. 간단히 말해서 고장 없이 잘 돌아야 한다는 것이다.
ko.wikipedia.org
이 말은 맞기도 하지만 보험회사의 광고와 비슷하게 내면을  들여다보면 조금 다른 점이 있다.
고장 나지 않는 시스템은 없으며, 버그 없는 소프트웨어는 없다는 것

작은 시스템 설계에서 가장 먼저 고려되어야 할 것은 이 고장들을 관리할 수 있는 수준으로 만들어야 한다는 것.

2

공사가 들어갈 때 일단 콘크리트를 치고 나면 바꾸기 힘든 부분이 있다. 특수한 부분에 설치되는 케이블 등은 再공사가 힘들다는 것이다. 보안전문가에게는 크레커에게 뚫려봤던 경험이 중요하듯이 책에서 모아서는 볼 수 없는 작은 것들이 성패를 결정한다. 이미 많은 책 내용의 사이사이에는 조금씩 노하우가 적혀있지만 무심코 지나치는 것들의 중요성을 아는 것. 사람들은 그것을 노하우라고 부르고. 공개하지 않으면 '비법'이 된다.

 네트워크 연결은 유선/무선을 다 고려해야 한다. 무선과 보안 기술이 많이 발전했기에 하드웨어상으로 문제가 발생했을 때 Alternative 할 수 있도록 구성을 해야 한다. LAN선은 100미터 넘게도 설치할 수 있겠지만 15M 이상 리피터나 게이트웨이, 허브 등의 구성이 없을 때는 노이즈가 낀다. 튀는 값 때문에 어느 순간에는 네트워크 오류가 자주 발생한다. 네트워크 계층에서 알아서 해 주겠지만 TCP/IP만 믿지 말자. 디버깅 단계에서는 모든 것을 믿어야 하고 설계 단계에서는 모든 것을 의심해야 한다. 비용이 높아질 수 도 있기 때문에 임베디드 장비에 무선 기능 탑재는 옵션으로 두고 사람 손이 닿을 수 있는 곳에 동글을 꽂을 수 있도록 하자. 침수, 누수, 랜선 불량 등으로 IT 문제 때문에 수십 년 뒤에 벽을 뜯어내는 수고로움을 덜기 위해서다. 

케이블은 기가비트 지원이 기본이겠지만 영상 용량이 4K로 가면서 계속 늘고 있기 때문에 10기가 이상 지원으로 할까? 

IDC에서의 최근 트렌드를 검색해 보고 비용 문제와 고려해서 선택해 보자. 이미 해봤다고 자만하지 말고 최신 트렌드를 계속해서 검색하고 이미 검증된 부분과 아닌 부분들을 잘 선택해야 한다.

3

소프트웨어 설계할 때 고려해야 할 것은 고장이 나면 어느 파트에서 고치는 것이 가장 빠르고 비용이 적게 들어  가는가?이다. SI 중에 임베디드 장비와 연결된 부분이 있다. 영상 PLAY 부분인데. 이 프로젝트에서는 임베디드 장비에서 다운로드 후에 영상을 PLAY 할지. 스트리밍으로 지원할지 2가지 옵션이 있었다. 연결 장비가  수십대였는데 중앙에서 관리가 가능해야 했다. 영상을 보내주는 서버는 따로 구성을 하는 것이 맞다. 확장 가능한 것도 이유겠지만 백업 플랜에서 저 비용으로 갈 수 있기 때문이다. 여기서 왜 기술 공개를 잘 안 하는지에 대한 이유가 나온다. 백업 플랜을 아마존 Unlimit 같은 개인 클라우드로 저 비용을 노린다고 하면? 한국 상륙한 아마존이 뭔가 대책을 세우지 않겠나... 물론, 이 시스템에서도 고려를 하고 있고. 또 B2B 기업들이 가격이나 내부 정책을 바꾸었을 때의 대안도 마련되어 있어야 한다. 자잘한 툴의 경우 삼성같이 큰 기업에서도 엔터프라이즈 라이센스가 아닌 개인 라이센스로 개발하는 일이 많았었다.

 이번 시스템에서는 대세에 따라 스트리밍으로 가는 것이 맞다는 의견이 있었지만 이번 프로젝트에서는 임베디드 장비에서 PLAY 되는 것으로 했다. 대신 Bittorrent나 곰플레이어가 지원하는 기능처럼  다운로드하면서 플레이가 가능하도록 해야 했다. 그 이유는 임베디드 장비 1대가 고장 났을 때는 1대만 영상 PLAY가 되지 않는다. 그러나 제어 서버나 영상 서버가 고장 났을 때는 전체 PLAY에 문제가 생긴다. 중앙 서버의 고장을 고칠 동안 받은 영상들은 PLAY를  계속되도록 해야 마치 고장 나지 않은 것처럼 보일 수 있다. 임베디드 장비에 어느 정도 코드를  집어넣을 수 있는, 프로그래밍을 할 수 있는 수준이라 많은 고민들은 임베디드 장비로 이관하기로 하였다.
 이렇게 되면 중앙 제어 서버도 특정 정보들만 제공을 하는 수준이고 임베디드 장비가 알아서 서버에 접속하고 서버에서 환경설정 정보를 받아오고 스스로 판단해서 행동하게끔 되어 있다. 가령 자동으로 전원 on/off 하는 경우도 메시지 큐 형식으로 서버에서 상태 변화  configuration  format을 만들면 임베디드 장비가 필요한 시간에 스스로 접속해서 정보를 읽고 사람들이 있는지 영상 PLAY는  어디까지 되었는지 등 알아서 판단하고 전원을 off 하는 것이다.
 고가용성을 지키기 위해서는 시스템 간 연결고리를 최대한 느슨하게 해 주어야 한다. 이것 하나만 생각하면 된다.

4

이제 고려해야 할 것은 소프트웨어 개발의 문제점이다. 나 역시 기업에서 개발팀장을 하고 있는 입장이지만 프로젝트 성공을 위해서는 사람을 다룰 줄 알아야 하고. 실무적 측면에서 사람 다루는 데는 돈, 혹은 기술 밖에는 없다. 비전만 맞추고 각자 알아서 일하면 되는 큰 시스템과는 달리 작은 시스템에서는 전통적인 상하 방식이고, 장점은 그것이 실패했을 때 아랫사람에게는 책임을 지우지 않는 방식이라는 것. 즉, 독박쓰는 시스템이다. 그래서 보수가 괜찮다.
 나 역시 입으로 일하거나 일정한 위치 선점을 해서 내 것을 챙기면 된다. 그러나 그런 마음가짐으로는 설계를 할 수 없다. 최근 아키텍트가 코더에 비해 낮은 위상으로 인식되는 것도 이와 같은 이유에서이다. 최근 가트너에서 연락 왔을 때도 내가 컨택 채널로 고정되어 권한도 받았으나, 초기 세팅을 하고 실무는 실무자를 정해서 넘겨줬다. 어떤 포지션이던 중간자 역할이 되어 버리면 중간자 입장만 고수하고 문제 발생시 책임 소재를 한정 지어 버린다. 확실하게 일할 기회를 주기 위해서는 '중간자'를 만들면  안 된다. 물론, 그 기회를 받고 싶은지 물어봐야 한다. 강압적으로 책임감이 요구되는 일을 주고 결과를 보면 대부분 '퇴사'로 이어진다.
법적으로 이미 폐지된 연대보증 시스템을 싫어하기는 하지만  '무한책임'에서 오는 긴장감은 본받아서 유지해야 한다는 것.

자, 그럼 초기 셋업 코드는 내가 짜 놔야 한다. 해당 프로젝트가 시작되면 결국엔 실무자가 가장 잘 알기 때문에 프로젝트 중반부터는 담당 엔지니어가 잘하면 잘할수록 설계자의 말을 듣지 않는다. 그러나 초기 코드를 만든 사람의 발언은 먹혀든다. 그래서 초기 코드를 만들거나 초반에 도움을 많이 줘야 프로젝트를 성공시킬 수 있다. 어차피 대부분은 실패해도 덕지덕지 붕대 감아서 무조건 성공시켜야 하는 입장이기는 하다.

초기 코드는 생짜로 전부 직접 짜지 않는다. sourceforge, github 등을 검색해서 쓸만한 애들을  찾아본다. 그리고 라이브러리 구매는 기본이다. 메신저나 화상회의 같은 경우 라이브러리를 사면 손쉽게 구축이 가능하다. 고급 개발자 1달 인건비만 따져보더라도 남는 장사고, 고급 개발자가 한 달 안에 구현해도 품질 테스트에 시간이 또 걸린다. 시간은 가장 큰 '비용'의 척도고, 돈을 쓰는 사람 입장에서는 가격보다는 기간을 더  중요시한다. 4계절이 뚜렷한 국내 사정은 더 심각하다. 공사가 포함된 프로젝트는 계절도 고려해야 한다. 그래서 견적을 뽑을 때 프로젝트 기간을 길게 잡고 싸게 저렴하게 하면 더 선택을 잘 할 거라 생각하는데 그렇지 않다. 대기업과 계약도 그렇다. 그들과의 계약은 모두 비공개이기에 제대로 공개되지 않는다. 한 가지 분명한 점은 결과물을 빨리 보여주고 초반에 빨리 달릴 수 있는 팀을 더 선호한다. 그리고 대기업 구조상 지인을 이용하는 것이 아니라면 그럴  수밖에 없다. 여담이지만 대기업과의 거래에서는 진행에 따라 많게는 70%까지 단가를 후려치는데  대기업 프로젝트를 했던 작은 IT 기업들이 그것을 포트폴리오로 표현할 때는 돈을 많이  벌었다고 생각하면  안 된다. 이름을 남기기 위해 남는 게 거의 없지만 프로젝트를 성공적으로 마쳤다. 정도로 이해하면 된다. 애플스토어의 액세서리들도 애플이 70% 먹는다. 

초기 코드들을 구하더라도 기존 유지될 시스템과 마이그레이션이 가능한지 안 한지 잘 따져봐야 한다.
msdn.microsoft.com
지금도 MSDN Subscription에서 찾을 수도 없는 Visual Studio  6로 시판되는 프로그램을 생산해 내고 있다. 그러나 Windows 10에서 문제가 점점 나오기에 64비트 빌드 옵션으로 처리를 하긴 했다만 언젠가는 마이그레이션 해야 한다. Visual Studio 마이그레이션 하면서 느끼는 점은... 버전 간의 호환성이 없다고 보면 된다는 것이다. 어느 정도 규모가 되고 라이브러리와 연계된 프로젝트는 자동으로 마이그레이션 된 적이 단 한 번도 없다. 개인적으로 말하자면 2008이 명작이고, 2008은 아마 향 후 5년은 더 지원할 것이라 본다. 다만 새로 진행하는 프로젝트는 2015로 갈  수밖에 없다. 최소 10년은 바라봐야 하기 때문이다. 일 년 일 년이 판이하게 다른 스타트업에서의 플레이와는 별개의 세계다.
www.visualstudio.com
Visual Studio 가 무료가 될지 누가 알았겠나?

PostgreSQL: File Browser        
www.postgresql.org
좋은 DB도 무료로 쓸 수 있게 될지...

7-Zip    
www.7-zip.org
데이터 압축도 오픈소스를 선택하고

github.com
오래된 녀석도 여러 라이브러리를 이용해서 손볼 수 있다.

dev.windows.com

시스템 구축에 MAC을 쓰면 참... 깔끔하고 좋겠지만. 아시다시피 리눅스가 가장 오픈적 형태이고  그다음은 윈도즈 기반이다. 오픈이 되면  될수록 고객의 입맛에 맞게 커스터마이징을 할 수 있다. 아쉽게도 BSD나 System V 계열처럼 저널링 파일 시스템을 쓰는 친구들은 계량기 및 전기 공사를 다시 하고 UPS도 달아줘야 해서 최소 2천만 원의 단가가 올라간다. 안정적인 서버에 UPS는 필수겠지만 견적서에 융통성을 두지 않으면 경쟁력이 없다. 게다가 전기세까지 고려하는 시스템은 필요 없는 시간에 전원을 끌 수 있게 설계가 되어야 한다. UPS가 들어가면 벽면과의 공간은 2.5Cm만 뛰우면 된다. 생각보다 작게 띄워도 된다. 벽면과의 거리면 지켜주면 되고 바닥에 놓아도 관계없다. 타워 렉의 가장 하단에 놓는 것도 좋다. 정말 중요한 온도는 0~40를 넘지 말아야 한다. 강한 햇빛, 겨울철 전열기 복사열, 습한 곳에서의 설치는 문제가 된다.


Fin.


업계에서 공개적으로 인지도가 있거나, 여러 프로젝트들과 괜찮은 퍼포먼스로 브로커와 손쉽게 연락이 닿는 사람들에게는 별 어쭙잖은 생각들일지도 모르나. 분명, 도움이 될 사람이 있다는 가정 아래 글을 썼다.

버그의 종류

서두에 밝힙니다. 브런치에 글을 적고 있지만 책 내용에 들어갈 수도 일부만 참고될 수도, 판이하게 다를 수도 있습니다. '대한민국 프로그래머'의 공식 공개 초판은 6월 2일 업로드 됩니다. 그 외 글들은 책과는 무관하게 시간 날 때 마다 생각을 씁니다.

버그의 종류는 많습니다. Coverity 툴 처럼 몇 억씩 줘야 쓸 수 있는 툴의 경우 버그의 종류를 수백가지 지정하고 개발자가 디버깅 하도록 도와줍니다. 힘든 시간이었지만 디버깅 관련해서는 정말 많은 경험을 쌓을 수 있었던 추억입니다. 스타트업 있을 때도 도입하려고 시도했지만 규모가 작아서 안된다고 하더라구요. 디버깅 만큼 연차가 묻어나고 레벨 구분이 확실히 되는 작업도 드뭅니다. 우리 부모님들께서 우리들을 생각해서 하는 한마디 한마디 처럼요. 짧지만 평생을 갑니다.

자바만 해도 Syntax Error, NullPointerException, Array Index, Argument, NumberFormat, 런타임 때 나는 Resource leak, OOM(Out of Memory) 등 다양하게 있지만 제가 생각하는 오류는 크게 4가지 입니다.

컴파일 오류

실행 파일을 만들기 전에 나는 오류 입니다. 컴파일, 링크가 요즘엔 대부분 컴파일이나 빌드로 통합해서 부르니 컴파일 오류라고 했습니다. 컴파일러가 잡아 낼 수 있는 오류입니다. 대표적인데 구문 에러, 인자를 잘못 넣거나, 언어가 요구하는 문법을 틀린 경우 입니다. 링킹할 때는 대부분 라이브러리를 못 찾아서 납니다.

런타임 오류

실행 중에 나는 것입니다. 휴대폰으로 치면 휴대폰이 갑자기 꺼지거나(reset, null pointer exception이 대표적), 멈추거나(특정 파트에서 무한루프를 도는 거죠) 합니다. 다시 켰는데 안되는 경우는 OOM(아우럽메모리) 혹은 파일 시스템이 망가진 경우 입니다. 모두 작동중에 제대로 Exception 처리를 안해줘서 그런거죠. (특히나 저장 용량이 다 차서 부트 영역까지 잡아 먹는 건 파티션을 나눠서 해결해줘야 합니다. 어플리케이션에서 케어할 수 없는 문제라는 거죠)

Algorithm 오류

역시 실행 중에 납니다. 그러나 가장 잡기 어려운 버그 입니다. Algorithm 이 뭔지 말했으니 알고리즘 오류라고 하겠습니다. 메소드 오류, 펑션 오류, 객체의 행동 오류등 다양하게 불러도 됩니다. 사람이 잘못 짠 것 입니다. 컴파일, 링크가 잘 되는데 원하는 결과값이 안나오는 경우 입니다. 

철학의 오류

대학생 멘토링을 하면서 얻은 새로운 오류 입니다. 화려한 경력으로 치장된 개발자들이 잘못 가르쳐 주기 때문에 나는 오류 입니다. 명문대 출신의 졸업반 친구들이 비전공자이지만 자기 인생을 걸고 전직(직업을 바꾸는)을 합니다. 캐릭터 스탯을 다시 찍어야 하는 경우 입니다. 이 때 가장 중요한 것이 자신감을 가지는 일입니다. 자신감을 가지려면 열심히 하면 되는데 독이 되는 사람들이 많습니다. 특히나 전공했던 사람들입니다. 
For Loop 실행 순서도 잘 모르는 사람이 많았습니다. 기초 문법도 모릅니다. Ctrl+C, V 에 무조건 최신 기술지향을 단 한번만 성공하면 됩니다. 비교 대상이 되지 않습니다. 우린 분야가 다르고 난 내 분야에 최고라고 말합니다. 생각 자체의 오류를 철학의 오류라고 합니다. 불륜을 조장하는 애슐리매디슨의 고객은 삼천만명이 넘습니다. 분명 우수한 시스템일 것이고 위에서 말한 오류들을 무수히 잡았을 것입니다. 그러나 철학적 오류가 있는 것입니다.



선행개발과 상품화팀에만 오래 있어서 직업병이 생긴 것인지도 모르겠습니다. 선행개발 직업병으로 볼 때, 최근 학생들이 하는 대부분의 새로운 프로젝트라고 하는 것들은 이미 오픈소스로 나와 있습니다. 그것을 가져와서 실행만 시키는 스크립트 키들이면서 새로운 도전이라 말합니다. 평가받기를 거부 합니다. 
상품화 직업병으로 볼 때 제대로 동작하지 않는 것을 최종 결과물로 내려고 합니다. 단 한번이라도 신박한 기능이 시연되는 것이 목표입니다. 차라리 만들지 않은 것이 낫다고 생각합니다. 우리 가족이 산에서 위급 상황을 만났는데 휴대폰 오류로 목숨이 위태롭다고 생각해 봅시다. 차라리 아무것도 만들지 않는게 낫습니다.

완전히 새로운 것에 대한 도전 정신을 가지던지, 처음 계획한 결과물을 완벽한 형태로 내던지. 그게 다니면 두마리 토끼를 잡기 위해서 멋진 아이템에 도전하며 밤낮으로 디버깅할 지언정, 목표로의 열망을 잃지 않던지. 온 힘을 다해야 하는데 참 답답함을 많이 느꼈습니다. 아닌 친구들도 많습니다. 반짝이는 눈빛 하나로 꿈을 말하는 친구들도많이 있습니다. 대체로 이런 현상이 난다는 것이죠. 반성합니다. 제대로 가르쳐 주지 못한 기성 세대의 잘못입니다.

국내에서 묵묵히 제품의 완성도를 올리기 위해 디버깅을 하고 있는 개발자 분들. 감춰진 소스와 자신의 문제가 아니라고 하는 정치 속에서도 일하시는 분들. 또, 국내 최고의 실력을 가지고 세계와 경쟁하는 분들은 책 쓸 시간이 없습니다. 이에, 재차 계속 권유를 드리고 있습니다. 저 역시 대한민국 개발 문화를 바꾸려고 시작했으니 한번 뽑은 칼은 끝까지 가보려고 합니다. 문화를 만들어 보려고 합니다.

책 소개

모든 언어의 아버지 격인 C를 만든 데니스 리치 & 브라이언 커니핸의 저서는 “The C programming language”입니다. 이 책의 차례에만 “pointer” 단어가 10번 이상 나옵니다.
그만큼 중요한 개념입니다. 다른 모든 언어를 이해할 때도 포인터가 가장 기초적 개념이 됩니다. 심지어 이기종의 언어들(JAVA, SWIFT 等)도 포인터 개념으로 이해할 수 있습니다.

포인터

 포인터를 만드는 키워드는 * (Asterisk, 애스터리스크)입니다. point는 “가리키다”는 뜻이며, pointer는 “가리키는 것”입니다. 명확한 정의는 “메모리의 특정 주소를 가리키는 것”입니다. [주소]를 가리키기에 보통 아파트 [주소]에 비유합니다.
메모리 주소의 경우
0xb000001
로 표현하고
아파트 주소의 경우
수원시 영통구 영통로 154번길 자바아파트 108동 1004호
의 식으로 표현 합니다.
포인터와 아파트 주소의 다른 점이 있습니다.
포인터의 경우 아파트 평수도 고려를 해야 한다는 것입니다.

포인터 변수의 크기는 우편번호와 같이 고정 길이로 되어 있습니다.                    

printf("%d %d\n", sizeof(char), sizeof(char*));
printf("%d %d\n", sizeof(short), sizeof(short*));
printf("%d %d\n", sizeof(int), sizeof(int*));
printf("%d %d\n", sizeof(long), sizeof(long*));
printf("%d %d\n", sizeof(float), sizeof(float*));
printf("%d %d", sizeof(double), sizeof(double*));

백설표 설탕 마크(*)가 붙은 모든 변수는 타입에 관계없이 32bit 컴파일 時 4byte, 64bit로 컴파일 時 8byte 입니다. 왜냐면 특정값을 말하는 것이 아니라 메모리 주소값을 말하기 때문입니다. 프리미티브 변수의 경우 변수 자체가 "변수에 들어있는 값"을 의미합니다.

int s = 88;
int *o = &s;
printf("%d, %d, %d, %x, %d", s, o, &s, &o, *o);

포인터 변수 o 에 변수 s 의 메모리 주소값을 담고 있습니다. 즉 88=s=*o 입니다. &s = o 입니다. &o 의 경우 포인터 변수 o 자체의 주소를 말합니다. 메모리 특정 공간에 4byte, 64머신이라면 8byte의 공간을 차지하고 있습니다.
모든 프로그램은 메모리에서 실행이 되기 때문에 포인터는 그 이름 그대로 메모리의 모든 공간을 가리킬 수 있습니다. 다만, 얼마만큼의 공간을 가리키는지 정해줘야 합니다. int 형의 경우 int형 포인터로 선언해서 같은 공간을 가리킬 수 있습니다. char 형의 경우 char 포인터 변수를 이용해서 가리키면 됩니다. int와 long 형 모두 4byte 이므로 int형을 long 형 포인터로 가리켜도 관계 없습니다. 아파트 주소와는 다르게 아파트 평수도 함께 기입을 해 줘야 합니다. 즉, int *s; 의 경우 *s 부분만 아파트 주소를 말합니다.

수원시 영통구 영통로 154번길 자바아파트 108동 1004호 (24평)

처럼 평수를 기입하는 것이 바로 *s의 앞부분에 있는 int 입니다.
이처럼 int 형은 안다는 것은 메모리의 개념은 연속적인 것이라서 특정 부분까지 읽어야 한다는 뜻입니다. 예를 들어, “아버지방구” 를 메모리에 적재했을 때 다음과 같이 연속적 공간에 적재된다고 봅시다.

|아|버|지|가|방|구|

3글자를 읽으면 아버지가 되고
5글자를 읽으면 아버지가방이 되고
6글자를 읽으면 아버지가방구 가 됩니다.
읽는 크기에 따라서 얻을 수 있는 값이 달라진다는 뜻입니다.

포인터 연산자

포인터 관련 연산자는 단 두가지 입니다. & 의 경우 주소값을 말하며, *의 경우 값을 말합니다.
둘은 정반대의 개념입니다.                    

int s = 88;
printf("%d", *&*&*&*&*&*&*&*&*&*&*&s);

이와 같은 경우 printf 안에 &*...s 는 단순히 s를 적어 준 것과 같습니다.

리틀 엔디안, 빅 엔디안

CPU는 무조건 둘 중 하나의 바이트 오더를 가집니다. 옵션으로 선택을 할 수 도 있고, 처음부터 고정되어서 나오기도 합니다. 여기서 unit(단위)이 byte 단위라는 것이 가장 중요한 포인트 입니다. int 형 4바이트(혹은 8바이트)수를 char 형 포인터 변수로 읽는다면 앞 부분 혹은 뒷 부분의 일부분 밖에 읽을 수 없습니다. 앞 부분/뒷 부분의 방식은 CPU의 endian 방식에 따라 달라집니다. 빅 엔디안의 경우 우리가 생각하는 그대로의 모습입니다. 그러나 리틀 엔디언의 경우 그 반대 입니다.                    

int n = 0x11223344;
printf("%x\n", (char)n);
의 경우 44가 남습니다.

데스크톱 컴퓨터에서 가장 많이 쓰는 Intel CPU의 경우 리틀 엔디언 입니다. 0x 로 시작하는 16진수 숫자 하나의 경우 4비트를 표현할 수 있습니다. 8비트가 1바이트니 “44”라는 2개의 숫자가 나오는 것입니다.
포인터 변수의 경우 메모리의 주소 값을 가지고 있는 특수 목적 변수입니다.

&p의 경우 포인터 변수 자체의 주소값을
p의 경우 가리키는 곳의 주소값을
*p의 경우 가리키는 곳의 주소에 담긴 실제 값을 가리킵니다.

CPU의 특성에 모든 언어는 영향을 받습니다.
심지어 플랫폼에 종속적이지 않다는 JAVA, 전세계에서 가장 많이 쓰는 운영체제인 Android 조차 데이터 구조를 설계하고 네트워크로 보내는 부분이 들어가면 엔디안 변환도 필요한 프로젝트가 있습니다. 운영체제도 CPU가 제공하는 API로 만들어진 프로그램에 불과하기에 그 위에 만들어진 프로그램도 마찬가지로 영향을 받습니다. 프레임워크라고 거창하게 불리는 것도 하나의 프로그램일 뿐입니다.

다중 포인터

주소값을 대입할 때 다음과 같이 중첩해서 대입할 수 있습니다.                    

int s = 88;
int *o, **o2o, ***o2o2o, ****o2o2o2o, *****o2o2o2o2o, ******o2o2o2o2o2o;
o = &s;
o2o = &o;
o2o2o = &o2o;
o2o2o2o = &o2o2o;
o2o2o2o2o = &o2o2o2o;
o2o2o2o2o2o = &o2o2o2o2o;
printf("%d\n", ******o2o2o2o2o2o);

배열 포인터                    

int s[3][6] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 };
int (*o)[6] = s;
printf("%d ", o[1][1]);
배열 포인터라는 단어에서 앞부분은 “배열”, 뒷부분은 “포인터” 입니다.

복잡한 배열과 포인터의 해석에서는 뒷 단어만 보면 됩니다.
배열 포인터는 포인터 입니다.

소스 코드는 어떻게 해석할까요? 변수명에 *가 있으면 포인터 []가 있으면 배열 입니다. 변수명이 ()로 싸여져 있다면 () 안만 보면 됩니다. *[]가 같이 있다면 우측에 붙는 []를 우선시 합니다.

int ( * read ) ( struct block_device *blockdev, uint64_t block, ...) //(함수) 포인터 입니다.

int *s[8];     //배열 입니다.
int (*s)[8];  //포인터 입니다.
int *(s[8]);  //배열입니다.

말로 장황하게 설명된 부분을 볼 때 마지막 단어만 보면 됩니다. 배열에 포인터에 포인터에 배열에 포인터의 배열이라고 하면… 가장 뒤에 배열이 있으니 “배열” 입니다. 배열 포인터는 배열을 가리키는 포인터 입니다. 가리킬 때 “아파트 평수”를 같이 말해줘야 합니다. 아파트 평수는 별로 선호하는 비유가 아니므로 이후부터는 “메모리 공간을 바라보는 크기”, 줄여서 “크기”라고 표현 하겠습니다. 배열의 크기가 6이므로 *o는 6개의 단위로 끊어서 메모리를 바라보게 됩니다. printf 값은 7이 나오게 됩니다. 그럼, 항상 이렇게 값을 알려줘야 할까요? “아버지가방구”처럼 마음대로 읽고 싶을 수도 있습니다. 물론, 프로그래머 마음 입니다.

int s[3][6] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 };
int **r = s;
printf("%d", *(int*)r+15);

이와 같이 대입 후 r 의 크기(메모리 공간을 바라보는 크기, 아파트 평수, 아버지방구)를 int 형으로 지정 후에 끊어서 읽으면 됩니다. 15번째 값이므로 15가 출력 됩니다.
printf("%d", *(int*)r+15); 는 다음과 같이 바꿀 수도 있습니다.                    

printf("%d", ((int*)r)[15]);
[] 는 +와 * 의 역할을 합니다.

포인터 배열                    

int s = 88;
int *o, **o2o, ***o2o2o, ****o2o2o2o, *****o2o2o2o2o, ******o2o2o2o2o2o;
o = &s;
o2o = &o;
o2o2o = &o2o;
o2o2o2o = &o2o2o;
o2o2o2o2o = &o2o2o2o;
o2o2o2o2o2o = &o2o2o2o2o;
printf("%d\n", ******o2o2o2o2o2o);
int *p[6] = { o, o2o, o2o2o, o2o2o2o, o2o2o2o2o, o2o2o2o2o2o };
printf("%d\n", s);
printf("%d\n", *p[0]);
printf("%d\n", *(int*)*p[1]);
printf("%d\n", *(int*)*(int*)*p[2]);
printf("%d\n", *(int*)*(int*)*(int*)*p[3]);
printf("%d\n", *(int*)*(int*)*(int*)*(int*)*p[4]);
printf("%d", *(int*)*(int*)*(int*)*(int*)*(int*)*p[5]);

위에서 설명한 것과 같이 “포인터 배열”의
뒷 부분만 읽으면 “배열” 입니다.
포인터를 담는 배열로 생각하면 되겠습니다. 포인터를 하나씩 꺼내어 참조했던 값들을 따라가서 모두 88이 출력되는 프로그램 입니다.

동적 할당                    

int *a = (int*)calloc(9, sizeof(int));
int y[8] = {0, 1, 2, 3, 4, 5, 6, 7};
for (int i = 0;i < 8;i++) {
 a[i] = y[i];
}
printf("%d\n", y[6]);
printf("%d\n", a[6]);
y[6] = 88;
printf("%d\n", y[6]);
printf("%d\n", a[6]);
free(a);
a = y;
printf("%d", a[6]);
동적 할당은 메모리 공간 확보로 이해하면 됩니다. 성능이 크게 중요하지 않다면 malloc 보다 calloc를 사용하는 것을 권장 합니다.. 메모리 공간을 확보하고 배열을 이용하여 값을 복사합니다. 포인터 변수 a는 단지 확보된 메모리를 가리키는 변수일 뿐입니다. 메모리를 해제하고 a를 y가 가진 메모리를 가리키고 바뀐 값을 보여주는 예제 입니다.

함수 포인터

지금까지는 포인터와 배열만 나누었습니다. 왜냐면 메모리의 입장에서는 배열, 포인터, 함수포인터, struct, class 모두 같은 개념 입니다. 메모리에서 바라보는 "크기"의 문제입니다. 프로그래머도 메모리로 생각하는 눈을 가진 다면 데이터와 함수, 멤버변수와 메소드, struct와 function 모두 같은 개념으로 다가 옵니다.
배열은 데이터를 편하게 넣고 읽기 위해, 포인터는 프로그램을 유연하게 만들기 위한 목적으로 주로 사용됩니다. 특정 주소에 bit 값을 써야하는 임베디드 프로그래밍 영역을 제외하고는 객체지향을 보는 목적으로 함수 포인터를 보면 되겠습니다.

함수 포인터는 객체 지향의 주요 구성 요소 입니다. struct 와 typedef 를 이용하여 간단히 객체 지향 패턴을 구현해 보면 다음과 같습니다.


......

......

......



씨와 자바의 공통점

객체지향언어라는 공통점을 가지고 있는 씨뿔뿔과 자바의 공통점을 찾기는 어렵지 않습니다. C 와 JAVA의 공통점을 하나하나 짚어보면서 다른 프로그래밍 언어의 공통점을 알고 소통하는 프로그래밍을 해 보도록 합시다.

Null Pointer Exception

C는 포인터를 가지고 있습니다. 포인터 값에 null 이 배정되고, null 포인터로 발생하는 문제 상황을 null pointer exception 이라고 합니다. null pointer exception 은 null pointer error 라고도 말을 합니다. error가 exception을 포함하는 개념이기 때문입니다. 이에 모든 error가 exception은 아니지만, 모든 exception은 error 입니다. error 와 exception 모두 프로그램을 종료 시킬 수 있기에 크게 구분하지는 않습니다. exception이 error 보다 작은 개념이고 개발자가 컨트롤 할 수 있는 부분이라고 보시면 됩니다.            
#include

void main() {
 int *s = NULL;
 *s = 8;
}
이는 다름과 같이 Null Pointer Exception을 일으킵니다.
아주 오래전 개발되었던 “씨앗" 언어 이후에는 한글로 된 소스를 찾아보기 어려웠습니다. JAVA의 창시자인 James Gosling 의 저서 [The Java Language Specification] Chapter 3. Lexical Structure에 전세계 언어를 지원하는 UTF-16 이야기가 나옵니다. 이에 한글로 클래스 명을 작성하였습니다. 변수, 메소드 역시 한글 사용이 가능합니다.             
package javatest;

public class 널포인터익셉션 {
 public static void main(String[] args) {
  Object s = null;
  System.out.println(s.getClass());
 }
}

이 프로그램을 실행시킨 결과는 다음과 같습니다.                    


Exception in thread "main" java.lang.NullPointerException
 at javatest.널포인터익셉션.main(널포인터익셉션.java:6)
java.lang.NullPointerException 이라고 되어 있습니다. 포인터가 없는 언어인데 왜 Null Pointer 라는 표현을 쓸까요?

자바 언어의 소스 코드 중 JavaExceptions.c 파일을 보면            

jthrowable
createThrowableFromJVMTIErrorCode(JNIEnv * jnienv, jvmtiError errorCode) {
    const char * throwableClassName = NULL;
    const char * message            = NULL;
    jstring messageString           = NULL;

    switch ( errorCode ) {
        case JVMTI_ERROR_NULL_POINTER:
                throwableClassName = "java/lang/NullPointerException";
                break;

errorCode가 JVMTI_ERROR_NULL_POINTER 일 때 NullPointerException 메시지를 출력합니다.
C에서 NULL은 0 입니다. JAVA에서는 무엇일까요?                    

NULL_CHECK(e, JVMTI_ERROR_NULL_POINTER);

NULL_CHECK를 0인지 아닌지로 판별하고 있습니다. 역시 0 입니다.                    

#define NULL_CHECK0(e) if ((e) == 0) return 0
#define NULL_CHECK(e) if ((e) == 0) return

언어적 철학으로는 NULL(C11), null(JAVA), nil(Objective-C), nullptr(Visual C/C++) 등 다양하게 표현하고 있지만 실상은 모두 0 입니다. 개념 분리를 위해 각 언어들이 노력하지만 단순히 ‘없다’는 표현이 맞고, 0과 1의 컴퓨터 세계에서는 0으로 이해하는 것이 다양한 프로그래밍 언어를 이해하는 첫 걸음 입니다. JAVA 역시 C/C++로 짜여져 있습니다. C++ 창시자 비얀 스트라스트럽이 가장 먼저 만든 프로그램은 C++ 코드를 C로 만들어 주는 번역기 였습니다. 이미 근본적으로 C, C++, JAVA는 같은 언어일 수 밖에 없습니다. 우리는 프로그래밍 언어들을 꿰뚫는 개념을 보는 방법을 알아야 합니다.

Class와 Struct

C/C++ 프로그래머는 이미 class 와 struct의 공통점을 알고 있습니다. struct 의 경우 public 접근 제한, class의 경우 private 접근 제한을 가진다는 것 외엔 동일합니다.            

#include
#include

struct Cpluscplus {
 int age;
 char name[16];
};

void main() {
 Cpluscplus cppStruct;
 cppStruct.age = 33;
 strcpy_s(cppStruct.name, "이소라");
 printf("%s(%d)", cppStruct.name, cppStruct.age);
}
struct 를 class로 고친다면 다음과 같이 고칠 수 있습니다.            
#include
#include

class Cpluscplus {
public:
 int age;
 char name[16];
};

void main() {
 Cpluscplus cppStruct;
 cppStruct.age = 33;
 strcpy_s(cppStruct.name, "이소라");
 printf("%s(%d)", cppStruct.name, cppStruct.age);
}

자바의 경우 다음과 같습니다.            

package civa;

public class 자바클래스 {
 int age;
 String name;
 public static void main(String[] args) {
  자바클래스 o = new 자바클래스();
  o.age = 33;
  o.name = "이소라";   
  System.out.println(o.name+"("+o.age+")");
 }
}
age 와 name은 C, C++, JAVA에서 동일하게 멤버 변수로 불립니다.
class 안에 들어 있는 method는 어떤 개념으로 이해를 해야 할까요? setter/getter 가 들어가는 경우 struct와의 공통점을 어떻게 이해를 해야 할까요?            

package civa;

public class 자바클래스 {
 int age;
 private String name;
 public void setName(String name) {
  this.name = name;
 }
 public String getName() {
  return this.name;
 }
 //타 클래스에서 만들어진 main 으로 간주  
 public static void main(String[] args) {
  자바클래스 o = new 자바클래스();
  o.age = 33;
  o.setName("이소라");   
  System.out.println(o.getName()+"("+o.age+")");
 }
}

이 클래스는 C에서 이렇게 구현 됩니다.

bootcamp 지우기

맥북 프로 레티나 터치바 diskutility 에서 bootcamp 파티션 삭제하면 검은색에서 회색으로 바뀐다(APFS로 지정) 파틴션 아이콘 클릭하여 - 버튼을 이용하여 삭제하면 끝.