배열의 이름은 주소이다.
2023.1.9. 최초 작성
2023.1.11 내용 추가
배열은 연속적인 메모리 공간을 가지는 자료구조이다. int 변수 5개를 만드는 것은 변수 이름이 5개 필요하고 할당된 메모리 공간이 따로 떨어져 있을 수도 있다.
배열은 항상 연속적인 메모리 공간을 갖는 것을 보장하고 이름이 1개만 있는데 각각을 번지로 접근할 수 있다.
연관성이 높은 데이터를 배열로 관리하면 읽고 쓰는 속도가 빠르다. 코딩할 때 가장 유리한 장점은 번지로 사용 가능하고 번지는 정수이므로 반복문으로 제어가 가능하다는 것이다.
아래 코드는 길이가 5인 정수 타입 배열을 사용한 예이다.
#include <stdio.h>
void main() {
int arr1[5] = {1, 2, 3, 4, 5};
printf("%d\n", arr1[0]);
printf("%d\n", arr1[1]);
printf("%d\n", arr1[2]);
printf("%d\n", arr1[3]);
printf("%d\n", arr1[4]);
//arr1[5] = 6; // 없는 번지에 값 입력
//printf("%d\n", arr1[5]); // 에러가 나지 않는다.
}
위 코드에서 주석 처리한 2줄은 없는 번지를 사용하고 있다. 보통 다른 언어들은 이런 경우 존재하지 않는 번지를 사용하려 시도해서 에러가 난다. 하지만 C언어는 개발 환경에 따라 에러가 없거나 간단한 경고만 하고 실행해준다.
[주석 사용한 실행 결과]
1
2
3
4
5
주석 제거 후 실행하면 에러가 나더라도 무시하면 실행된다. 그래서 배열을 사용할 때는 번지를 주의해서 사용해야 한다.
[주석 제거 후 실행 결과]
1
2
3
4
5
6
아래 코드는 배열에 값을 대입하기 위해 다양한 방법을 사용하였다.
#include <stdio.h>
#include <string.h>
void main() {
int arr1[5] = {1, 2, 3, 4, 5}; // 전부 초기화
int arr2[5] = {1, 2, }; // 일부만 초기화
int arr3[5]; // 초기화하지 않음
int arr4[5];
for (int i = 0; i < sizeof(arr3) / sizeof(int); i++) {
arr3[i] = 3; // 반복문을 통해 값 대입
}
memset(arr4, 0, sizeof(arr4)); // 메모리의 특정 범위를 바이트 단위로 값을 채워줌
for (int i = 0; i < 5; i++) {
printf("arr1[%d] : %d\n", i, arr1[i]);
}
for (int i = 0; i < 5; i++) {
printf("arr2[%d] : %d\n", i, arr2[i]);
}
for (int i = 0; i < 5; i++) {
printf("arr3[%d] : %d\n", i, arr3[i]);
}
for (int i = 0; i < 5; i++) {
printf("arr4[%d] : %d\n", i, arr4[i]);
}
}
[실행 결과]
arr1[0] : 1
arr1[1] : 2
arr1[2] : 3
arr1[3] : 4
arr1[4] : 5
arr2[0] : 1
arr2[1] : 2
arr2[2] : 0
arr2[3] : 0
arr2[4] : 0
arr3[0] : 3
arr3[1] : 3
arr3[2] : 3
arr3[3] : 3
arr3[4] : 3
arr4[0] : 0
arr4[1] : 0
arr4[2] : 0
arr4[3] : 0
arr4[4] : 0
memset 함수는 메모리의 특정 범위를 바이트 단위로 채워주는 기능이 있는데 string.h 헤더 파일이 필요하다.
주의할 점은 0이 아닌 다른 값이 들어가면 예상한 값이 아닌 다른 값이 들어 있을 수 있다. memset 함수는 바이트 단위로 값을 넣기 때문에 주의해야 한다.
int 타입이 4byte일 경우 다음처럼 숫자 1은 왼쪽에 31bit는 모두 0이고 오른쪽 끝 bit만 1이다.
00000000 00000000 00000000 00000001
10진수로 표현하면 1이다.
memset 함수는 1byte 단위로 값을 넣기 때문에 1로 int 크기만큼 값을 대입하면 다음과 같다.
00000001 00000001 00000001 00000001
10진수로 표현하면 16843009이다.
배열의 특징은 연속적인 메모리 공간을 가지고 이름은 1개이다. 대신 0부터 시작하는 정수 번지로 접근할 수 있는 것이다. 주의할 점은 존재하지 않는 번지도 접근할 수 있고 초기화된 값이 예상한 값이 아닐 수 있으므로 확인이 하는 것이 좋다.
또 다른 배열의 특징은 정해진 데이터 타입만 입력할 수 있다. 만약 다른 타입이 들어가더라도 이상한 값을 가질 수 있다. 그러므로 원한 값이 들어 있는지 자주 확인하는 것이 좋다.
배열의 이름은 배열의 첫 번째 번지 메모리 주소이다.
아래 코드는 위 문장을 확인하기 위해 만든 소스이다.
#include <stdio.h>
void main() {
int arr1[5] = { 1, 2, 3, 4, 5 };
printf("arr1 address &사용 : %p\n", &arr1);
printf("arr1 address &미사용 : %p\n", arr1);
for (int i = 0; i < sizeof(arr1) / sizeof(int); i++) {
printf("arr1[%d] address : %p\n", i, &arr1[i]);
}
}
[실행 결과]
arr1 address &사용 : 00000034385FFC58
arr1 address &미사용 : 00000034385FFC58
arr1[0] address : 00000034385FFC58
arr1[1] address : 00000034385FFC5C
arr1[2] address : 00000034385FFC60
arr1[3] address : 00000034385FFC64
arr1[4] address : 00000034385FFC68
int 배열이므로 번지마다 int 자료형의 크기만큼 차이가 나는 것을 확인할 수 있다.
배열의 자료형이 바뀌면 해당 자료형의 크기만큼 번지마다 차이가 날 것이다.
번지를 사용하지 않고 배열의 이름만 사용할 때 배열의 0번지와 주소가 같은 것을 확인할 수 있다. 이것을 통해 배열의 이름은 배열의 첫 번째 번지의 메모리 주소라는 것을 알 수 있다.
배열의 이름이 곧 주소이니 주소를 통해 아래 코드처럼 사용할 수도 있다.
#include <stdio.h>
void main() {
int arr1[5];
printf("숫자 5개를 입력하세요.");
for (int i = 0; i < 5; i++) {
scanf("%d", arr1+i);
}
for (int i = 0; i < 5; i++) {
printf("%d\n", *(arr1+i));
}
}
scanf로 키보드 입력에서 배열 이름 arr1 앞에 변수의 주소를 의미하는 & 연산자를 붙이지 않았다.
printf에서는 배열 이름 앞에 * 연산자를 붙여 사용하였다.
주소 앞에 * 연산자를 붙이면 그 주소에 들어있는 값을 사용할 수 있다.
주소연산자(&)와 역참조연산자(*)는 포인터에서 많이 사용하게 될 것이다.
배열의 이름을 이용해 연산하면 자료형의 크기만큼 주소를 증가시킬 수 있다.
정수 배열을 그림으로 나타내면 아래와 같다.

정수가 4byte일 경우 배열 이름인 arr1은 배열의 맨 처음 byte의 주소이다. 배열의 이름에 덧셈하면 배열의 자료형인 int 타입의 크기만큼 주소가 증가한다.