[sw정글 5주차] M1으로 C개발 해도 되나요?

Date:     Updated:

카테고리:

태그:

📚SW정글 5.7주차

이번글은, 정보전달 보다는 (고군분투)개발일지 느낌으로 작성하도록 하겠다. 어떻게하면 가장 좋은 C개발환경을 맞출 수 있을지 고민하며 알게된 사실들을 시간의 흐름대로 작성하였다.

🚀이글의 요약

  1. M1 valgrind 안돌아간다
  2. GCC컴파일러 M1에서 돌아간다
  3. assembly language 달라도 된다
  4. GCC컴파일결과물, LLDB에서 디버깅 된다
  5. Clang컴파일결과물, GDB에서 디버깅 된다
  6. Apple silicon으로 C개발 해도 된다

🚦발단

옆자리의 bulksup형이, AWS와 연결된 VScode로 무한재귀를 호출해버리고서는, 키보드에 샷건날리고 싶지만 꾹 참는듯한 모습을 보였다. 호출된 무한루프는 AWS EC2를 멈추게 만들었고, 형의 Vscode도, 터미널도, 형의 손도.. 모든게 멈추었다.

무한재귀님을 호출했다는 죄로, EC2 재부팅을 멍하니 기다려야 하는 것이다.

더 좋은방법이 없을까?

🚀일단 시작, GCC?

M1에 C/C++extension과 code runnerextension을 설치하였다. 코드들이 문제없이 돌아가는듯 해 보였다. 그럼 이제 gcc를 설치할 차례인것이다. 왜 코치님은 gcc를 설치하라고 하신걸까? 맥에는 clang이라는 (내생각에는..)훌륭한 컴파일러가 있는데 굳이 gcc를 설치해야 할까?

검색 결과, GCC보다 clang이 훨씬 적은 메모리를 사용하고, 훨씬 자세한 에러메시지한 힌트를 주고, 컴파일속도도 빠른 것 같았다. 그럼에도 불구하고…

clang

클랭의 홈페이지에 들어가면, C언어(C99)에 대한 지원을 "거의" 다 한다고 한다.

즉, 우리가 C로 웹서버도 만들고, C로 OS도 만들어야 할텐데, “거의”지원되는 clang 컴파일러로 어떠한 에러를 맞이하게될지 모르기 때문에 gcc를 사용하라고 못박아 두신 것이였다. 아 참고로 gcc는 하나의 컴파일러가 아니다. GCC(GNU Compiler Collection)즉, 컴파일러 모음집인것이다.

🤔M1에 GCC는 어떻게 설치하지?

M1에 GCC설치 및 VS연동에 관한글을 작성해두었다.

⁉️GCC는 깔았지만, Ubuntu를 사용하지 않아도 괜찮을까?

이제 같은 컴파일러를 사용한다.

컴파일과정

같은 C언어에 같은 컴파일러로 돌리면 같은 결과물을 내뱉을까? 친구들과 동일한 gcc11로 나온 결과물들은 성공적으로 동일한 결과물을 뱉아내지 않을까? 생각했다. 컴퓨터 구조가 다르므로, Linker는 다르게 작동할지언정, Assembly program의 코드는 동일하지 않을까? 라고 생각했다. 간단한 프로그램을 돌려보았는데, 완전히 다른 결과물이 나왔다. 알고보니 많은 사람들이 알고있는 당연한 결과였다.

void add(){
	int a, b, c;
	a =2;
	b = 2;
	c = a+b;
}

다음 그림은 위의 코드를 돌린 결과물이다.

그렇다 다른 Assembly program을 만들어 낸다.

💻Intel과 ARM

리눅스, GNU, 리처드 스톨만, 자유소프트웨어 사회 등등을 외치시는 분들… 사실 어쩔수 없게도, 당연하지만 X86아키텍쳐를 기준으로 만들어진 것이다. 즉 X86을 기준으로 GCC가 만들어졌으며, 내가 사용하고자 하는 GCC는 ARM아키텍쳐에 맞게, 특히 M1에서도 호환되도록 만들어진 GCC를 사용하는 것이다. X86을 기반으로 GNU프로젝트에서 출발된, 그 진정한 의미의 GCC는 아닌것이다.

그렇다면 어떤것들이 다를까?

apple_silicon

일단 컴퓨터가 다르게 생겨먹었다.다르게 생겨먹은 컴퓨터에 다른 명령을 내리는 것은 당연하다.

그럼 아키텍처에 대해 조금 더 자세히 이야기해보도록 하겠다. 인텔은 CISC(Complex Instruction Set Computing) ARM은RISC(Reduced Instruction) 아키텍처를 사용한다. CISC의complexed라는 단어에 주목해보도록 하자. 복잡하다고? 복잡하면 더 안좋은게 아닌가? 아니다. 복잡한 연산도 할 수 있다는 것이다.

예를들면, 203을 계산해본다고 하면, CISC에서는 pot(power of three)명령어로 세제곱을 연산할 수 있다. 하지만 RISC에서는 이와같은 명령어가 없을 수 있는 것이다. 그냥 무식하게 곱셈 명령어 3번으로 곱해나가는 것이다. 물론 mov, add와 같은 기초적인 명령어는 그 궤를 같이하지만, 이와같이 complexed(복잡한) 명령어에 대해서는 차이가 발생하는 것이다. 그리고 이 부분이 내가 우려하던 호환성에 대한 부분과 직결된다.

호환성을 따지기 전에 ARM에 대해 조금 더 알아보자면, ARM은 이처럼 간단한 단순작업을 빠르게 하자는 철학과 디자인이 들어가 있는 컴퓨터 구조인 것이다. 따라서 병렬연산을 쉽게할 수 있는것이다. 따라서, X86구조에서는 컴퓨터에게 내려주는 명령어의 길이가 제각각이여서 병렬처리가 어려운 것이다.

x_86 x86은 위 사진처럼 명령어의 길이가 제각각이기 때문에

x_86 parallel 위처럼 병렬처리를 하기위해서는 프로그래머들을 갈아서 넣어야 하는 것이다.

따라서, 어떤 게임을 돌릴 때 (인력을 갈아서)병렬처리를 하면 X-86에서 빠르게 작동할 수 있지만, 그렇게 만들어지지 않은 일반적인 게임 및 프로그램들은 싱글코어의 성능이 중요한 것이다.

arm 하지만 Arm의 경우 동일한 명령어 길이를 가지고 있기 때문에, 명령어를 언제 어디서 끊어야되는지 쉽게 생각할 수 있다. 그래서 쉽게 병렬처리를 할 수 있고 빠르게 작동하는 것이다.

추가적으로, 한번에 복잡한 계산을 처리하는 것 보다 간단한 계산을 여러번하는것이 트랜지스터의 에너지소모를 줄일 수 있기 때문에 에너지 효율이 좋다고 한다. (M1이 배터리가 오래가는 이유가..👍) 그렇다면, 복잡한 기능이 없기 때문에 들어가는 트랜지스터의 숫자도 적고, 더 작은크기, 더 저렴한 가격에 더 좋은 에너시효율성까지 갖춘 ARM은 무적일까? Intel은 끝이고 대 ARM시대가 도래한 것일까?

그렇지는 않다고 한다. CISC는 RISC의 장점을 살린 아키텍처를 일부 도입하고, RISC는 CISC처럼 복잡한 계산을 할 수 있는 커맨드들을 추가 해 나가고 있다. 둘의 경계가 애매해지고 있는 것이다.

👍그렇다면 ARM써도 되나요?

이야기가 많이 새어나갔지만 개발환경 셋팅에 대한 이야기를 계속 해 나가겠다. x86환경이 아닌 arm에서 C개발을 해나가도 될까? 고민이 생겼다. 검색결과 M1초기에는 일부 불만들이 있었지만, 현재는 각종 포럼에 GCC를 ARM에서 돌림으로써 나오는 이슈들은 보이지 않았다. AMD에 대한 중요성이 높아져가는 근 몇년 사이에 충분한 개발과 검증들이 이루어졌지 않나 추측한다.

그렇다면 코치님이 말하는 GDB는 호환이 될까? 결론적으로 m1에서는 GDB호환이 되지 않았다. 여타 ARM에서는 지원이 되지만 M1은 GDB지원이 되지 않았다.

일반적으로, Clang으로 컴파일을 하면 lldb 디버거를 사용하고, GCC로 컴파일을 하면 GDB를 사용한다. 그렇다면 GCC를 꼭 써야하는 나는 M1-GCC-lldb라는 끔찍한 혼종을 써야하는 것인가? 그리고 그렇게 해도 되는것인가?

🐞디버거의 호환성

디버거에 대해 공부한 결과 아주 긍정적인 결과를 얻을 수 있었다. GCC로 컴파일된 모든 결과물들은, LLDB에서 디버깅을 할 수 있으며, Clang으로 컴파일된 모든 내용들은 GDB에서 확인될 수 있다는 것이다.

어떻게 이런 호환성을 보장할 수 있을까?

DWARF라는 파일형식으로 결과값을 뱉아주기 때문이다. 컴파일러들에 -g옵션을 넣어주게 된다면 컴파일된 오브젝트에 디버깅 정보를 넣어줄 수 있다. 그리고 이에 대한 공통된 형식이 있기 때문에 다른 디버거에서도 읽어낼 수 있는 것이다.

GDB를 사용할 이유가 없는 것이였다

해당 객체와 메모리사용에 대한 정보를 lldb디버거에서 모두 확인할 수 있기 때문이다.

🐞디버깅을 해보자

5주차 과제인 RB-tree를 구현하며 lldb를 잘 활용할 수 있었다.

lldb사용법✨

lldb 사용법

  • breakpoint 추가
    • b (줄번호)
  • breakpoint 제거
    • br del (breakpoint 번호)
  • 출력
    • p (변수명), print (변수명)
  • 출력 고정으로 추가시켜놓기
    • display (변수명)
  • 다음으로 :n
  • 함수들어가기: s

display기능으로 내가 보고싶은 변수 xx.left에 대한 정보를 line-by-line으로 넘겨가며 확인하는 모습이다. GCC에서 컴파일했지만, lldb 디버거가 잘 읽어내는 모습니다.

🚰메모리 누수 확인

M1으로 RB-tree구현을 하면서 가장 아쉬웠던 점이다. 로컬에서의 빠릿빠릿함과 손쉬운 파일관리는 좋았지만 valgrind를 사용하지 못함에 대한 아쉬움이 있었다.

발그린드란 무엇일까? 발그린드 홈페이지를 들어가보니, (개발자들 홈페이지는 왜이렇게 다 못생겼..) 캐시분석, 메모리누수, 스레드 오류 등을 쉽게 확인할 수 있도록 도와주는 도구이다. (동적 분석도구를 구축하기 위한 도구) 아쉽게도 개발자피셜 M1에서는 사용이 불가능하다고 한다.

그렇다면 어떻게 사용할 수 있을까? mac에 내장되어있는 leaks커맨드를 사용해 간편하게 확인할 수 있다. 하지만 실행중인 파일에 대해서 검사가 가능하므로..

int main(void) {
  test_init();
  test_find_erase_rand(10000, 17);
  printf("Passed all tests!\n");
  //무한루프 삽입
  while(1){ }
}

위처럼 무한루프를 하나 삽입해주고, 터미널에 leaks (프로그램 이름)을 입력해주면 확인해볼 수 있다.

leaks1 역시 잘 돌아간다. 일부러 프로그램에 구조체 하나를 남겨두었기에 192바이트의 memory leak이 발생한 상태

leaks2

메모리 누수를 잡은 상태이다. 0이 나오며 잘 작동하는 모습이다.

👋요약 및 결론

🚀요약

  1. M1 valgrind 안돌아간다
  2. GCC컴파일러 M1에서 돌아간다
  3. assembly language 달라도 된다
  4. GCC컴파일결과물, LLDB에서 디버깅 된다
  5. Clang컴파일결과물, GDB에서 디버깅 된다
  6. Apple silicon으로 C개발 해도 된다

🚀결론

valgrind를 제외한다면 모든 기능들이 잘 작동하며, ubuntu에서 보다 훨씬 편하게 C개발을 이어갈 수 있다.



😵배우면서 깨달은 내용을 정리해 보았습니다. 틀린 것 같은 개념을 아래 댓글에 달아주시면 감사합니다😵

🌜 Thank you for reading it. Please leave your comments below😄

맨 위로 이동하기

swjungle survive 카테고리 내 다른 글 보러가기

댓글 남기기