포인터로 배열에 접근하는 또 다른 이점은 바이트 단위 조작에 있다. 배열이 어떤 형이던 1바이트 단위로 데이터를 조작할 수 있다. 메모리를 바이트 단위 조작이 가능하다는 것은 하드웨어 그 자체를 들여다 보는 것과 같다. 운영체제가 실행되면 모든 하드웨어/소프트웨어 정보들이 메모리에 올라와 있기 때문이다.
리누스 토발즈의 말처럼 C는 가장 하드웨어에 근접한 프로그래밍 언어라고 할 수 있다. 세상에 나온지 50년가까이 되는데 아직까지 현역으로 사용되고 있다는 것은, C가 여전히 하드웨어 레벨의 프로그래밍에 필요하다는 것을 말한다.
바이트를 다룰 수 있다고 해서 모든 데이터를 마음대로 움직일 수 있다는 것은 아니다.
음수나 부동소수점을 다루는 방법은 또 다른 문제다. 그 구조를 먼저 이해해야 하기 때문이다.
예제에서는 포인터를 이용해서 배열의 전 요소들에 어떻게 접근하는지 하나의 예를 보여준다.
#include <stdio.h>
void main(){
int arr1[5] = {0,};
arr1[0] = 0x11223344;
arr1[1] = 0x12345678;
arr1[2] = 0x77553311;
arr1[3] = 0x43217654;
arr1[4] = 0x14311341;
char *ptr = (char *) arr1;
int length = sizeof(arr1)/sizeof(arr1[0]);
for(int i=0; i < sizeof(arr1); i++){
printf("array [%2d] : (%X) ", i , *(ptr+i));
if((i+1)%4 == 0){
printf("\n");
}
}
}
32비트 정수형이 5개인 배열에 16진수로 할당한다. 16진수로 할당하는 이유는 1바이트에 16진수 두자리가 대응되기 때문에 메모리를 가장 직관적으로 조작가능한 진법이기 때문이다. 16진수 2승, 256가지 수를 표현가능하다. (0~255 혹은 -128~127) 32비트 정수형을 바이트 단위로 나누면 4바이트이고 16진수의 2승인 256 단위 4개를 조작 할 수 있다. 말이 길지만 메모리 하나하나에 직접 작업한다고 생각하면 된다.
인텔 x86계열 CPU의 명령어도 1바이트로 나눠져 있다. 바이트 단위의 명령을 CPU에게 보낸다.
예> 0x90 NOP(no operation 아무것도 하지 않음)
포인터를 조작하게 되면 컴퓨터의 움직임을 하나하나 까지 이해할 수 있게 되는 것이다. 어셈블리어도 기계어가 바탕이지만 다양한 라이브러리가 있는 C언어의 명령어로 메모리를 조작하는 것은 훨씬 강력할 수 밖에 없다.
x86 명령어 리스팅
포인터를 사용해서 배열의 각 요소를 바이트 단위로 접근할 수 있고, 또 값을 바꿀 수도 있다. 이번에는 합산을 해본다.
우선 배열을 만들고 누산을 위한 total 변수를 만들어 둔다.
for 문에서 포인터를 사용하여 total 에 배열 값을 누산한다. 원리를 알게되면 쉽다.
#include <stdio.h>
void main(){
int score[7] ={70,82,67,58,93,77,62};
int *ptr = score;
int total = 0;
int length = sizeof(score)/sizeof(score[0]);
for(int i=0; i < length; i++){
total += *ptr;
ptr++;
}
float average = (float)total/length;
printf("Total score : %d, Average score : %0.2f ", total,average );
}
포인터도 여러개를 쓰면 변수관리가 힘들어지니까 배열로 선언할 수 있다.
#include <stdio.h>
void main(){
int array[3] = {10,20,30};
int *ptr[3] = {&array[0], &array[1], &array[2]};
int length = sizeof(array)/sizeof(array[0]);
for(int i = 0; i < length; i++){
printf("%d : %p\n",*ptr[i],ptr[i]);
}
}
1단계는 포인터와 배열을 1대1로 대응 시켜서 사용하는 방법이다. 큰 이점은 없을 것 같고 배열을 중심으로 돌아가는 느낌이다.
*다음은 이차원 배열에 포인터를 사용한다. 문법이 조금 더 복잡하긴 하지만 이차원 배열의 메모리에도 문제 없이 접근이 가능하다.
#include <stdio.h>
void main(){
int array[2][3] = {0,};
array[0][0] = 12;
array[0][1] = 34;
array[0][2] = 56;
array[1][0] = 78;
array[1][1] = 89;
array[1][2] = 10;
int (*ptr)[3];
ptr = array;
printf("%d : %p\n",(*ptr)[0], &(*ptr)[0]);
printf("%d : %p\n",(*ptr)[1], &(*ptr)[1]);
printf("%d : %p\n",(*ptr)[2], &(*ptr)[2]);
printf("%d : %p\n",(*(ptr+1))[0], &(*(ptr+1))[0]);
printf("%d : %p\n",(*(ptr+1))[1], &(*(ptr+1))[1]);
printf("%d : %p\n",(*(ptr+1))[2], &(*(ptr+1))[2]);
}
#include <stdio.h>
int add(int a,int b){
return a + b;
}
int sub(int a,int b){
return a - b;
}
int mul(int a,int b){
return a * b;
}
int div(int a,int b){
return a / b;
}
void main(){
int (*p[4])(int,int) = { &add,&sub,&mul,&div};
char op[4] = { '+','-','*','/'};
for (int i=0; i<4; i++){
printf("%d %c %d = %d\n",7,op[i],3,(*p[i])(7,3));
}
}
함수를 포인터 처럼 사용할 수 있게 해준다. 함수를 줄세워서 루프를 돌릴 수 있다.