| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |
- LinkedList
- i2c
- SPI mode
- Sync FIFO
- ACK Polling
- 곱셈기 설계
- 비동기 FIFO 구조
- Async FIFO
- ARM
- C언어
- BSS
- cadence conformal eco
- booth algorithm
- keynote 사용법
- keynote 도형 복사
- FIFO 설계
- LEC
- booth multiplier 설계
- asynchronous fifo
- Cortex-M3
- lec check
- 연산 가속기 설계
- booth multiplier
- ISR
- keynote
- keynote 도형 회전
- malloc
- Keynote 표
- c
- 자료구조
- Today
- Total
JINTBEAT Design Life
C언어 - Struct와 -> 연산자 본문
1. struct란 ?
struct는 여러개의 다른 타입의 변수들을 하나의 그룹(구조체)로 묶어주는 사용자 정의 자료형이다.
주로 관련된 데이터를 하나로 묶어서 관리할 때 사용한다.
예시)
struct Point {
int x;
int y;
};
이렇게 정의하면 Point라는 구조체를 선언한 것.
struct Point p1;
p1.x = 10;
p1.y = 20;
2. -> 연산자란 ?
->는 구조체 포인터를 통해 구조체 멤버에 접근할 때 사용하는 간접 멤버 접근 연산자이다.
struct Point {
int x;
int y;
};
struct Point p = {10, 20};
struct Point *ptr = &p;
printf("%d\n", ptr->x); // p.x에 접근
여기서 ptr->x 는 사실 (*ptr).x와 같은 뜻이지만, ->를 쓰는 게 훨씬 간편하고 직관적이다.
3. FW 코드에서 -> 많이 쓰나 ?
펌웨어 코드에서는 하드웨어 레지스터 맵 구조체에 접근하거나, 드라이버 코드에서 동적 메모리로 할당된 구조체 포인터를 다룰 때
-> 는 거의 필수적으로 사용된다.
typedef struct {
uint32_t CTRL;
uint32_t STATUS;
} UART_TypeDef;
- unt32_t가 뭐지 ?
uint32_t는 C에서 사용하는 정수형 데이터 타입이고, 의미는 다음과 같다.
- u → unsigned (부호 없음)
- int → 정수
- 32 → 32비트
- _t → 타입(type)이라는 뜻의 접미사
즉, 32비트 크기의 부호 없는 정수형이다.
범위는 **0 ~ 4,294,967,295 (2³² - 1)**까지.
- 왜 uint32_t 를 쓰는가?
int, long 같은 기본 타입은 컴파일러나 플랫폼(32비트 vs 64비트)에 따라 크기가 다를 수 있다.
하지만 uint32_t는 항상 32비트로 고정되어 있어서, 하드웨어 제어, 펌웨어, 네트워크, 파일 포맷 등 정확한 크기가 중요한 상황에서 주로 사용해.
정의 위치는 <stdint.h> 헤더 파일에 정의돼 있다.
- 이 구조체는 UART 하드웨어 블록의 제어 레지스터(CTRL)와 상태 레지스터(STATUS)를 나타냄
- typedef를 통해 구조체 이름을 간단하게 UART_TypeDef로 쓸 수 있게 해줌
#define UART0 ((UART_TypeDef *)0x40004000)
- UART0은 주소 0x40004000에 위치한 UART 하드웨어 블록을 가리킴.
- UART_TypeDef *로 캐스팅해서, 구조체 멤버 접근이 가능하도록 함.
- 즉 이 주소는 하드웨어의 레지스터가 있는 실제 메모리 주소이다.
UART0->CTRL = 0x01; // UART0의 제어 레지스터에 접근
- 구조체 포인터 UART0을 통해 CTRL 레지스터에 0x01을 씀.
- 이는 실제로는 주소 0x40004000에 4바이트 쓰기 연산이 수행되는 것과 같다.
- 왜 4byte냐면... CTRL이 uint32_t로 정의되어 있으니까이다.
- 즉, 하드웨어 제어 명령이 직접 내려가는 코드이다.
- MCU나 SoC에서는 하드웨어 블록(UART, SPI 등)의 레지스터들이 특정 주소에 배치되어 있다.
- 예를 들어, UART0 컨트롤러의 레지스터는 0x40004000부터 시작할 수 있다.
- (UART_TypeDef *) : 형 변환(type cast)이다.
- C에서는 어떤 숫자를 포인터로 바꿔줄 때 이렇게 쓴다.
- 지금은 0x40004000이라는 주소를 UART_TypeDef *라는 구조체 포인터로 바꾸는 것이다.
#define UART0 ((UART_TypeDef *)0x40004000)
- UART0은 이제 UART_TypeDef 구조체를 가리키는 포인터가 된다.
- 실제로는 UART0->CTRL 처럼 접근하면, 주소 0x40004000을 기준으로, CTRL 필드는 그 주소,
STATUS는 0x40004004가 됨. 왜냐하면 CTRL이 4byte이기 때문이다.
4. 왜 형 변환을 해줘야 하지 ?
[1] -> 연산자는 "구조체 포인터"에서만 쓸 수 있다.
some_struct->member;
이 문법은 some_struct가 구조체 포인터일 때만 사용 가능하다.
typedef struct {
int x;
} Point;
Point p = {5};
Point *ptr = &p;
ptr->x = 10; // OK
p->x = 10; // ERROR (p는 구조체, 포인터가 아님)
0x40004000은 그냥 정수일 뿐이다. 그냥은 멤버 접근(->CTRL)이 불가능하다.
왜나면 C언어에서 정수는 구조체가 아니니까!
5. 형 변환을 하면 뭐가 바뀌는가 ?
(UART_TypeDef *)0x40004000
이렇게 하면 컴파일러에게 "이 주소는 UART_TypeDef 구조체가 시작하는 주소야"라고 알려주는 것이다.
즉, 해당 주소에 있는 데이터를 구조체처럼 해석해도 된다는 의미이다.
이제부터 UART0->CTRL처럼 쓸 수 있다. 이건 결국
*((uint32_t *)0x40004000) = 0x01;
와 같은 저수준 동작으로 바뀐다. (하지만 훨씬 보기 쉽고 관리하기 좋아진다)
- 예시 코드
#define MY_REG_ADDRESS 0x40000000
*( (uint32_t *)MY_REG_ADDRESS ) = 0x12345678;
[1] 0x40000000라는 주소를
[2] uint32_t * 형 변환해서
[3] 거기에 0x12345678을 쓰기(write) 하라는 뜻

'🖥️ - C language' 카테고리의 다른 글
| C언어 - 포인터 배열 (0) | 2025.05.12 |
|---|---|
| C언어 - 포인터 연산 (0) | 2025.05.12 |
| C언어 - 포인터와 배열 (0) | 2025.05.11 |
| C언어 - 포인터 (0) | 2025.05.10 |
| C언어 - 포인터의 포인터 (0) | 2025.05.09 |