1. gets 함수 - gets 함수는 글자를 무제한으로 받기 때문에 fgets를 쓰는게 좋다
(배열이 할당된 메모리 공간을 넘어서 다른 메모리 영역을 덮어쓰게 되는 버퍼오버플로우가 발생할 수 있다)


2. 포인터


포인터로 두 정수의 합과 평균을 구하는 코
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void) {
// 1. 변수 선언 및 초기화
int a = 10, b = 15, total; // 정수형 변수 선언 및 초기값 할당
double avg; // 실수형 변수 선언
// 2. 포인터 선언
int* pa, * pb; // 정수형 변수를 가리키는 포인터 pa, pb 선언
// 포인터 pt에 total 변수의 메모리 주소(&total)를 저장
// pt가 가진 값(&total)과 total의 주소는 같지만,
// pt 포인터 변수 자체가 저장된 주소(&pt)는 total의 주소(&total)와 다름.
int* pt = &total;
// 포인터 pg에 avg 변수의 메모리 주소(&avg)를 저장
// pg가 가진 값(&avg)과 avg의 주소는 같지만,
// pg 포인터 변수 자체가 저장된 주소(&pg)는 avg의 주소(&avg)와 다름.
double* pg = &avg;
// 3. 포인터에 주소 할당
pa = &a; // pa에 변수 a의 메모리 주소를 저장 (pa는 a를 가리킨다)
pb = &b; // pb에 변수 b의 메모리 주소를 저장 (pb는 b를 가리킨다)
// 4. 포인터를 이용한 값 연산 및 저장
// *pa: a의 값 (10), *pb: b의 값 (15)을 가져와 더함
// 그 결과(25)를 *pt가 가리키는 total 변수에 저장
*pt = *pa + *pb;
// *pt: total의 값 (25)을 가져옴. 2.0으로 나누어 실수 연산 수행
// 그 결과(12.5)를 *pg가 가리키는 avg 변수에 저장
*pg = *pt / 2.0;
// 5. 포인터를 통한 값 출력 (간접 접근)
printf("두 정수의 값 : %d, %d\\n", *pa, *pb);
printf("두 정수의 합 : %d\\n", *pt); // *pt는 total 변수의 현재 값(25)을 출력
printf("두 정수의 평균 : %.1f\\n", *pg); // *pg는 avg 변수의 현재 값(12.5)을 출력
// 6. 일반 변수를 통한 값 출력 (직접 접근)
// 포인터를 사용한 연산 결과가 변수에 잘 반영되었는지 확인
printf("두 정수의 합 : %d\\n", total);
printf("두 정수의 평균 : %.1f\\n", avg);
return 0;
}
포인터 연산을 활용1 - ary[i] 대신 *(ary + i) 처럼 포인터 연산을 사용할 수 있다
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main(void) {
int ary[3];
int i;
*(ary + 0) = 10; // ary[0]
*(ary + 1) = *(ary + 0) + 10; // ary[1]
printf("세 번째 배열 요소에 키보드 입력 : ");
scanf("%d", ary + 2); // ary[2] 에다가 키보드를 입력
for (i = 0; i < 3; i++) {
printf("%5d", *(ary + i));
}
return 0;
}
포인터 연산을 활용2 - 역참조 연산( *(a + i) ), 배열 인덱스 연산 (a[i]), 배열 이름 사용 (ary[i]) 이 세가지 방식으로 모두 동일한 배열 요소에 접근할 수 있다.
#include <stdlib.h>
#include <stdio.h>
int main()
{
int ary[3];
int* a = ary;
*a = 10;
*(a + 1) = 20; // ary[1]
a[2] = a[0] + a[1]; // ary[0] + ary[1] , *(a + 0) + *(a + 1)
for (int i = 0; i < 3; i++) {
printf("%-5d", a[i]);
}
printf("\\n");
return 0;
}
포인터 연산 활용2 에서 조사식을 보면 ary[1], a[1], *(a+1) 값이 다 같은걸 볼 수 있다.



포인터의 산술연산 - 가리키는 자료형의 크기를 고려하여 요소 단위로 이동한다
포인터 뺄셈 규칙: 동일한 자료형을 가리키는 두 포인터끼리만 뺄셈이 가능하고, 뺄셈 결과는 두 포인터가 가리키는 메모리 주소간의 차이를 그들이 가리키는 자료형의 크기 단위로 나눈 값이다
pa 는 배열의 첫번째 요소(ary[0])을 가리키는데, pa + 3 으로 첫 번째 요소의 주소에서 int 자료형 크기(4바이트) 만큼 3번 이동한 것이다. 따라서 pb는 ary[3]의 주소를 저장한다
pa++ 로 pa 주소값을 int 자료형 크기(4바이트) 만큼 증가시킨다. 따라서 pa는 ary[1]의 주소로 이동한다.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int ary[5] = { 10, 20, 30, 40, 50 };
int* pa = ary;
int* pb = pa + 3;
printf("pa : %u\\n", pa);
printf("pb : %u\\n", pb);
pa++;
printf("pa : %u\\n", pa);
printf("pb - pa : %u\\n", pb - pa); // 포인터 변수끼리의 산술 연산
printf("앞에 있는 배열 요소의 값 출력 : ");
if (pa < pb)
printf("%d\\n", *pa);
else printf("%d\\n", *pb);
return 0;
}
배열의 함수 전달
함수에 배열을 전달할 때 호출하는 쪽에서는 배열의 이름만 넘겨도 되지만 호출받는 쪽(함수)에서는 배열의 주소를 받기 위해 매개변수를 포인터 변수(int* pa)로 선언해야 한다
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void print_ary(int* pa);
int main() {
int ary[5] = { 10, 20, 30, 40, 50 };
print_ary(ary); // 배열을 함수로 전달, 배열을 넘길 때는 포인터 변수로 전달한다
return 0;
}
// 전달 받은 배열을 함수에서 출력
void print_ary(int* pa) {
for (int i = 0; i < 5; i++) {
printf("%d ", pa[i]);
}
}
배열 최댓값 찾기
배열의 시작 주소를 포인터로 함수에 전달한다.
주석처럼 int pa[]로 선언해도 컴파일러는 내부적으로 int* pa로 처리한다고 한다.
INT_MIN을 사용하면 가장 작은 값을 얻을 수 있다
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <limits.h>
void print_max(int* a); // void print_max(int pa[])
int main() {
int array[7] = { 4, 5, 8, 1, 2, 3, 7 };
print_max(array);
return 0;
}
void print_max(int* a) { // void print_max(int pa[])
int max = INT_MIN; // 변수의 Scope
for (int i = 0; i < 7; i++) {
if (max < a[i]) {
max = a[i];
}
}
printf("가장 큰 값: %d\\n", max);
}
다양한 크기의 배열 출력
배열의 주소와 그 크기를 인수로 받아 전달받은 크기만큼만 정확하게 요소를 순회하며 출력한다 (유연성, 재사용성)
C 함수가 배열의 크기를 자동으로 알 수 없어 (배열 이름은 주소만 전달하고 크기 정보는 소실된다고 한다) 크기 정보를 명시적으로 함께 전달해야 한다
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <limits.h>
void print_ary(int* pa, int size); // void print_max(int pa[])
int main() {
int ary1[5] = { 10, 20, 30, 40, 50 };
int ary2[7] = { 10,20,30,40,50,60,70 };
print_ary(ary1, 5);
printf("\\n");
print_ary(ary2, 7);
return 0;
}
void print_ary(int* pa, int size) { // void print_max(int pa[])
for (int i = 0; i < size; i++) {
printf("%d ", pa[i]);
}
}
최대값 구하기
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <limits.h>
// 함수 선언
void input_ary(double *pa, int size);
double find_max(double *pa, int size);
int main() {
double ary[5];
double max;
int size = sizeof(ary) / sizeof(ary[0]); // 배열 길이 계산 (call-by-value로 전달될 예정)
// ary의 시작 주소가 함수에 전달됨 → call-by-reference
input_ary(ary, size);
// ary 주소를 참조하여 최댓값 계산 → call-by-reference
max = find_max(ary, size);
printf("배열의 최댓값 : %.1lf\\n", max);
return 0; // 프로그램 종료 → program counter가 OS로 돌아감
}
// ----------------------------------------
void input_ary(double* pa, int size) {
// pa는 ary의 시작 주소를 가리킴 (call-by-reference)
printf("값을 입력해주세요: ");
// 전수조사(반복문): 배열 전체에 대해 입력 수행
for (int i = 0; i < size; i++) {
// &pa[i]는 pa가 가리키는 배열의 i번째 원소 주소
scanf("%lf", &pa[i]);
}
// 함수 종료 시 스택 프레임이 pop됨 → context switch(함수 레벨)
}
// ----------------------------------------
double find_max(double* pa, int size) {
double max = INT_MIN; // 초기 최솟값 설정
// 전수조사(반복문): 모든 요소를 확인하여 최댓값 찾음
for (int i = 0; i < size; i++) {
if (pa[i] > max) {
max = pa[i]; // call-by-reference 덕분에 실제 배열값 참조 가능
}
}
// max를 call-by-value로 main에 반환
return max;
}
대소문자 구하기
아스키 코드값을 알고있지 않아도 'a' 값과 'A' 값의 차(32)로 대소문자 변환이 가능하다
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main() {
char small;
char cap = 'G';
if ((cap >= 'A') && (cap <= 'Z')) {
small = cap + ('a' - 'A');
}
printf("대문자: %c\\n", cap);
printf("소문자: %c\\n", small);
return 0;
}
인코딩 방식은 다음과 같이 있다
문자 인코딩 방식의 특징 보충 설명
| 인코딩 | 특징 | 바이트 크기 (Byte) | 한국어 환경에서의 역할 |
| 아스키코드 (ASCII) | 영문, 숫자, 기호 등 **7비트(Bit)**를 사용하는 가장 기본적인 표준. | 영어 1바이트 | 모든 현대 인코딩의 기반이 됩니다. |
| 유니코드 (Unicode) | 전 세계의 모든 문자를 하나의 표준 번호(코드 포인트)로 정의. | 코드 포인트 정의 | 전 세계 언어를 통일적으로 처리하는 표준입니다. |
| UTF-8 | 유니코드를 저장하는 가변 길이 인코딩 방식. | 영어 1바이트, 한글 3바이트 | 웹과 리눅스 등 대부분의 현대 시스템에서 표준으로 사용됩니다. |
| UTF-16 | 유니코드를 저장하는 인코딩 방식. | 영어 2바이트, 한글 2바이트 | 주로 Windows 환경이나 자바(Java) 등 일부 애플리케이션의 내부 처리에서 사용됩니다. |
| EUC-KR (CP949) | 한글 인코딩 표준이었으나, MS에 의해 확장됨. | 영어 1바이트, 한글 2바이트 | 과거 한국어 Windows 시스템과 MS Office(엑셀 등)에서 사용되던 CP949 인코딩의 기반이 되었습니다. |
대문자, 소문자, 숫자, 특수문자 갯수 세기
문자열을 한글자씩 반복해서 검사하고, 각 문자가 어떤 종류에 속하는지 아스키 코드 범위를 이용하여 분리한다
strlen 안 쓰고 null 문자가 나타날 때까지 for문 돌려서 갯수 세는 방법도 있다고 한다
fgets 대신 scanf 쓰면 개행이 있는지 체크를 따로 안해도 된
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main() {
char str[80];
int small = 0;
int cap = 0;
int number = 0;
int special = 0;
printf("문자를 입력해 주세요: ", str);
fgets(str, 80, stdin);
printf("총 길이 : %d ", strlen(str));
for (int i = 0; i < strlen(str); i++) {
char s = str[i];
if (s >= 'a' && s <= 'z') {
small++;
}
else if (s >= 'A' && s <= 'Z') {
cap++;
}
else if (s >= '0' && s <= '9') {
number++;
}
else if (s != '\\n') {
printf("특수문자가 찍히는 문자 : %c..", s);
special++;
}
}
printf("알파벳 대문자: %d\\n", cap);
printf("알파벳 소문자: %d\\n", small);
printf("숫자: %d\\n", number);
printf("특수문자: %d\\n", special);
return 0;
}
fgets와 scanf의 개행 문자 처리 차이
fgets 대신 scanf 쓰면 개행이 있는지 체크를 따로 안 해도 되는듯
이것은 fgets와 scanf가 사용자 입력의 줄 바꿈(\n) 문자를 처리하는 방식이 다르기 때문에 발생하는 현상입니다.
📝 차이점 설명
| 함수 | 개행 문자 (\n) 처리 방식 | 문자열 포함 여부 | 직전 코드에 미치는 영향 |
| fgets | 줄 바꿈 문자(\n)를 문자열의 일부로 함께 읽어와 저장합니다. | 포함 | 마지막 \n을 특수문자 카운트에서 제외(if (s != '\n'))해야 합니다. |
| scanf | scanf("%s", str) 형식으로 사용할 경우, 줄 바꿈 문자(\n)를 읽기 전에 멈추고 버립니다. | 불포함 | 문자열 끝에 \n이 없으므로, 특수문자 카운트에서 $\textbf{따로 제외할 필요가 없습니다.}$ (fgets의 단점을 해결) |
💡 scanf 사용 시 주의점
scanf를 사용할 경우 \n 처리에 대한 걱정은 줄어들지만, 다음과 같은 치명적인 단점이 있습니다.
- 공백 문자 처리 불가: scanf("%s", str)는 **공백(Space)**을 만나면 그 앞까지만 읽고 멈춥니다. 따라서 "Hello World"를 입력하면 str에는 "Hello"만 저장됩니다.
- 버퍼 오버플로우 위험: scanf는 문자열의 최대 길이를 지정하는 안전 장치가 없어, 사용자가 배열 크기보다 긴 문자열을 입력하면 버퍼 오버플로우가 발생하여 보안 문제가 생길 수 있습니다.
결론: 문자열에 공백이 포함될 가능성이 있다면 fgets가 더 안전하고 적합하며, fgets의 특징인 \n 제거 처리(str[strlen(str) - 1] = '\0';와 같은 코드)만 추가해주면 됩니다.
포인터의 순차적 접근
반복문을 돌면서 포인터 pa 가체를 증가시켜 메모리 주소를 이동시키고, 이를 통해 배열의 모든 요소를 차례대로 읽어낸다
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int ary[3] = { 10, 20, 30 };
int* pa = ary;
int i;
printf("배열의 값 : ");
for (i = 0; i < 3; i++) {
printf("%d ", *pa);
pa++;
}
return 0;
}
'c' 카테고리의 다른 글
| 이중포인터 N차원 배열 (1) | 2025.11.10 |
|---|---|
| 문자열 (0) | 2025.11.07 |
| 배열 (0) | 2025.11.05 |
| 함수와 배열 일부 (0) | 2025.11.04 |
| 반복문 (while, for) (0) | 2025.11.03 |