배열 주소 연속
배열의 주소는 메모리에서 연속된 공간을 차지한다.
즉, arr[0], arr[1], arr[2] ... 이런 식으로 각 원소의 주소가 일정한 간격을 유지하며 배치된다.
배열의 주소 구조 예제
int arr[3] = {10, 20, 30};
printf("%p\n", &arr[0]); // ex) 0x1000
printf("%p\n", &arr[1]); // ex) 0x1004 (int는 4바이트)
printf("%p\n", &arr[2]); // ex) 0x1008
- int는 4바이트이므로, 주소는 4씩 증가한다.
- char 배열은 1바이트씩, double 배열은 8바이트씩 증가한다.
배열이 연속된 주소를 가지는 이유
- CPU가 캐시를 효율적으로 활용할 수 있도록 하기 위함.
- 배열의 인덱스를 기반으로 빠르게 메모리 접근이 가능하도록 하기 위함.
포인터 배열
포인터 배열은 각 원소가 변수의 값이 아니라, 변수의 주소를 저장하는 배열이다.
즉, 포인터를 여러 개 저장하는 배열이라고 보면 된다.
포인터 배열 예제
int a = 10, b = 20, c = 30;
int *arr[3] = {&a, &b, &c}; // 포인터 배열
printf("%d\n", *arr[0]); // 10
printf("%d\n", *arr[1]); // 20
printf("%d\n", *arr[2]); // 30
- arr는 int* 타입의 배열이며, 각 원소는 int 변수의 주소를 저장한다.
- *arr[i]를 하면 해당 주소에 저장된 값을 가져올 수 있다.
포인터 배열이 유용한 경우
- 2차원 배열을 포인터 배열로 표현할 때
- 문자열 리스트를 저장할 때
- 함수 포인터 배열을 만들 때
arr[var]이 warning이 나는 이유
- 배열 인덱스는 항상 정수형이어야 함 (int, short, long 등)
- 실수형(double, float)을 인덱스로 사용하면 warning 발생
- scanf()로 입력받을 때는 항상 정수형을 사용해야 함
- 문자(char)나 포인터(pointer)를 인덱스로 사용하면 오류 발생 가능
- 배열 범위를 벗어나는 입력을 방지하기 위해 추가적인 체크가 필요함
포인터 간 +, - 연산
포인터끼리 덧셈(+)은 의미가 없어서 허용되지 않는다.
하지만 포인터끼리 뺄셈(-)은 같은 배열 내에서의 거리(차이)를 구하는 데 사용할 수 있다.
포인터 뺄셈 예제
int arr[5] = {10, 20, 30, 40, 50};
int *p1 = &arr[4];
int *p2 = &arr[1];
printf("%ld\n", p1 - p2); // 3 (4번째 요소 - 1번째 요소)
- p1 - p2는 두 포인터가 몇 개의 요소 차이가 나는지 반환한다.
- 이 결과는 바이트 단위가 아니라, 배열의 요소 개수 단위로 계산된다.
포인터 덧셈이 안 되는 이유
int *p1, *p2;
int *result = p1 + p2; // ERROR: 의미 없는 연산
- 포인터끼리 더하면 메모리 주소를 알 수 없는 값이 되므로 컴파일러가 허용하지 않는다.(말 그대로 쓸 데가 없다. 어따 쓸건데?)
포인터 vs 배열의 차이
배열과 포인터는 비슷해 보이지만, 동작 방식이 다르다.
int arr[3] = {10, 20, 30};
int *ptr = arr;
- arr은 상수 포인터처럼 동작하지만, 실제로는 배열 자체의 주소를 의미한다.
- ptr은 포인터 변수이므로 다른 주소를 저장할 수도 있다.
함수 포인터 배열
함수를 저장하는 포인터 배열도 사용할 수 있다.
void f1() { printf("Function 1\n"); }
void f2() { printf("Function 2\n"); }
void (*funcArr[2])() = {f1, f2};
funcArr[0](); // Function 1
funcArr[1](); // Function 2
- funcArr은 함수 포인터 배열이며, funcArr[i]()를 호출하면 해당 함수가 실행된다.
부동소수점 사용 소수 비교
종류 | 표현방식 | 특징 | 예제 |
정규수 (Normal Number) | (-1)^S × 1.M × 2^E | 일반적인 부동소수점 수 | 3.14, 1.0e-10 |
비정상수 (Denormal Number, DN) | (-1)^S × 0.M × 2^E_min | 연산이 느리지만 작은 값 표현 가능 | 1.0e-39, 5.0e-324 |
0 (Zero) | 부호 비트만 다름 | +0과 -0 구별됨 | 1.0 / -0.0 = -Inf |
무한대 (Infinity, Inf) | 특수한 지수 비트 값 | 오버플로우 발생 시 나옴 | 1.0 / 0.0 = Inf |
NaN (Not a Number) | 비트 패턴이 특정 값 | 정의되지 않은 연산 결과 | 0.0 / 0.0, sqrt(-1.0) |
정규수는 일반적인 부동소수점 수고, **비정상수(DN)**는 아주 작은 값을 표현하기 위해 사용.
비정상수는 정규수보다 작은 값을 저장할 수 있지만, 연산 속도가 느려지고 정확도가 떨어질 수 있음.
0은 +0과 -0을 구별하며, 0으로 나누면 무한대가 발생할 수 있음.
무한대(Inf)는 오버플로우 발생 시 나타나는 값이고, NaN은 잘못된 연산 결과를 의미.
이런 특수한 값들은 부동소수점 연산에서 오류를 방지하고, 작은 값이 0으로 사라지는 문제(언더플로우)를 해결하는 데 중요.
연산자 우선순위
우선순위 | 연산자 | 설명 | 결합 방향 |
1 | ( ), [ ], -> , . | 함수 호출, 배열 접근, 구조체 멤버 접근 | > |
2 | ! , ~ ,++, --, + , - , * , & , sizeof, (type) | NOT(논리, 비트), (후위, 전위) 증가, 감소, 부호, 역참조, 주소, 크기 확인, 형 변환 | < |
3 | *, /, % | 곱셈, 나눗셈, 나머지 | > |
4 | +, - | 덧셈, 뺄셈 | > |
5 | <<, >> | 비트 시프트 | > |
6 | <, >, <=, >= | 비교 연산자 | > |
7 | ==, != | 동등 연산자 | > |
8 | & | 비트 AND | > |
9 | ^ | 비트 XOR | > |
10 | | | 비트 OR | > |
11 | && | 로직 AND | > |
12 | || | 로직 OR | > |
13 | ? : | 3항 연산자 | < |
14 | =, +=, -=, *=, /=, %=, >>=, <<=, &=, ^=, |= | 대입 연산자 | < |
15 | , | 쉼표 연산자 (순차 평가) | > |
Declaration vs. Definition
Declaration vs. Definition
가장 큰 차이 = Memory Allocation(메모리 할당)
Declaration(선언)
메모리 할당 X
컴파일러에게 변수,함수 등의 존재나 형태를 알리는 것
Definition(정의)
메모리 할당 O
선언된 변수, 함수 등의 각 요소를 실제로 구현하는 것
#include <stdio.h>
// Declaration
struct food;
int swap(int *a, int *b);
int main() {
int *a;
int *b;
// Definition
struct food {
int sushi;
int cake;
int bread;
} FOOD;
FOOD.sushi = 3;
int c = 10;
int d = 15;
a = &c;
b = &d;
printf("%d\n", FOOD.sushi);
swap(a, b);
return 0;
}
int swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
추가로 warning 보게끔 설정 후 컴파일
gcc -Wall -Wextra -o program program.c
아래처럼 컴파일 시 'warning' 출력.
이유는 int 함수인 swap에서 리턴을 하지 않았다.(리턴 할 필요가 없는 함수)
이럴 땐 void 사용. 안해서 warning
program.c: In function 'swap':
program.c:31:1: warning: control reaches end of non-void function [-Wreturn-tyoe]
}
^
프로그램 실행
$ ./program
정상 동작
3
15
10
Compile
Preprocessing
$ gcc -E hello.c -o hello.i
The preprocessing stage is handled by the preprocessor, not the compiler. It prepares the source code before the actual compilation begins by processing directives that start with #.
- #include <stdio.h> → Inserts the contents of stdio.h into the source code.
- #define → Replaces macros with their defined values.
- Removes all comments from the code.
The output of this stage is typically a file like hello.i, which contains the processed source code.
Compilation
$ gcc -S hello.i -o hello.s
The compilation stage takes the preprocessed code (hello.i) and converts it into assembly code (hello.s).
Key tasks in this stage:
- Parses and checks the C code for syntax errors.
- Performs code optimization.
- Translates the code into platform-specific assembly instructions.
Assembling
$ gcc -c hello.s -o hello.o
An assembler converts the assembly code into machine code (object file).
Key tasks:
- Translates .s file to binary object code.
Linking
$ gcc hello.o -o hello
The linker combines one or more object files and external libraries into a single executable.
Key tasks:
- Resolves symbol references (e.g., function calls).
- Connects standard libraries (like printf from libc).
Library
A library is a file that contains reusable code such as functions and variables to avoid repetitive coding and reduce the size of a program.
Category | Static | Dynamic |
Link Time | Complie-time | Runtime |
Extension | .a | .so |
Size | Large | Small |
Modification | Recompilation required | Replace library file |
Speed | Fast(code already included) | Slightly slow(loaded at runtime) |
Memory Usage | High | Low |
'( * )Engineering > 🌜C/C++' 카테고리의 다른 글
C : const / break 없는 switch (0) | 2025.03.31 |
---|---|
C : void / main / 동적 할당 / list / inline (0) | 2025.03.16 |
C : 변수 / 2의 보수 / 형식 지정자 / Jump Table (0) | 2025.03.11 |
[ATmega 128] 3. 7 Segment 타이머 만들기. (1) | 2024.03.29 |
[ATmega 128] 1. LED 점등 (1) | 2024.03.29 |