[c] 전화번호부 만들어보기
Why does scanf ask twice for input when there's a newline at the end of the format string?
#include <stdio.h> #include <stdlib.h> #include <string.h> char *method1(void) { static char a[4]; scanf("%s\n", a); return a; } int main(void) { char *h = metho...
stackoverflow.com
겪은 문제 중에 scanf가 자꾸 2번의 입력을 받으려 한다는 사실을 알았다.
확인해보니 scanf함수를 다음처러 사용했다.
scanf("%s\n", name);
위를 보면 입력 문자형 구분란에 '\n'줄바꿈 문자를 넣어버렸다. 즉, 줄바꿈 문자로 입력의 끝을 알아채는 scanf에 미리 줄바꿈을 넣어서 scanf가 오작동해버린 것이다.
구조체를 사용하여 배열을 만들었다.
파일 입출력 시스템으로 텍스트 파일과 연동하였다.
다른 파일들과 헤더를 만들어 공유해보았다.
빌드할때 사용한 모든 스크립트의 c소스를 연동시켜줘야한다.
해시 테이블을 통해 만들었다.
다만 해시가 너무 빈약해 키가 겹쳐버린다면 데이터 저장을 못하도록 일단 만들었다.
fgets를 통해 문자열을 입력 받았다. scanf로 받을 수 있는 문자열의 개수를 한정할 수 있다.
단, 한정한 이상의 문자열이 들어온다면 필요한 만큼만 잘라가지만 남은 문자열이 버퍼에 그대로 남아있다.
즉슨, 다음에 오는 scanf가 입력을 받으러 오자마자 남아있던 잔여 문자들로 인해 오류가 발생해버린다.
하여 매번 사용하기 전에 버퍼를 지워주는 것이 좋다. 하지만 정수를 입력 받기 위해서는 scanf를 사용해야한다. 물론 gets로 문자열을 입력받아 형변환을 해주어도 되지만 일단 둘을 같이 써보는 상황을 겪어보고 싶었다.(그럴일이 없는게 최고다.) 그래서 CleanBuffer함수를 만들어 getchar함수로 입력 버퍼의 문자가 줄바꿈이 아니라면 계속 받아서 제거하는 방법을 사용했다.
stack smashing detected
위 오류를 만났다. 뭐 읽는 그대로 해석해도 메모리 참조를 잘 못한 것 같았고 적중했다.
문자열을 담은 배열을 선언하고 해당 배열의 값을 넣는 곳에 1씩 범위를 벗어 나고 있었다.
strtok를 활용하여 문자열을 분리해보았다.
뭔가 함수 자체가 그렇게 좋아 보이지는 않았다. 하지만 splite이 없는 상황이라 어떻게든 사용해봤는데, 문자열을 2번 나누기에 실패하여 문자열의 저장 방식을 하나로 통일하고 어차피 들어오는 데이터의 개수가 정해져있기에 3개씩 읽어들이도록 바꾸었다. 확실히 꼼수인 방법이기에 다시 도전해봐야한다.
[C언어/C++] strtok 함수(문자열 자르기)에 대해서.
안녕하세요. BlockDMask 입니다.오늘 공부할 함수는 문자열을 일정 기준을 정해서 싹둑싹둑 자를 수 있는 strtok 함수입니다.C언어 strtok 함수에 대해서 한번 알아보러 가보겠습니다. 1. strtok 정의와
blockdmask.tistory.com
C 언어 코딩 도장: 45.1 문자를 기준으로 문자열 자르기
45 문자열 자르기 지금까지 문자열을 복사하거나 붙이는 방법을 알아보았습니다. 이번에는 주어진 문자열을 자르는 방법을 알아보겠습니다. 참고로 문자열 자르기는 포인터를 이용하는 방식이
dojang.io
아래 코드에 info의 크기를 잘 구했다고 착각하고 strcpy가 아닌 strcat으로 바로 입력하려 했다.
strcat을 하면 자동으로 크기를 늘려 사용할 것이라 착각해버렸다. 그리고 바로 몰매를 맞았다. 문자열들이 다 깨벼저리는 것이다. 분명 크기는 충분히 늘렸고 여유분도 주었다. 왜그럴까 생각을 해보았다.
둘이 비슷하다 생각했지만 전혀 그렇지 않았다. (둘다 내가 짠 코드이면서 말이다...)
fn_strcat코드를 보면 가장 먼저 포인터가 string1의 끝으로 달려버린다. 즉, 시작하자마자 포인터는 char* 배열의 가장 뒤에 있는 것이다. 그러니 이름이 들어와도 바로 할당된 주소 밖으로 나가버린것이다..... 개멍청한 것 같다.
그래도 코드 까자마자 알아 차려서 다행이다.
정수형은 sprintf 함수를 사용하여 문자열로 바꾸고 cat으로 연결해주었다.
파일 입출력에 "a"권한은 파일이 존재하는지 판단하여 없으면 새로 생성한다. 단, "a"와 다른 권한이 섞여서 작동하지 않는다.
"a"로 파일 여부를 확인 후 "rw" 권한을 사용하자.
추가로 해당 함수에서 많은 애를 먹었다.
대부분 메모리 할당량이 부족하여 발생한 메모리 주소 참조 실패로 떴던 오류이다.
특히, sprintf부분에서 num의 값을 생각보다 훨씬 넓게 잡아주어야 (long long) 오류가 나지 않았다. 컴파일러마다 크기가 달라질터이니 조금 더 크게 잡아두는 것이 좋을 것이라 본다.
최종 코드
메인 코드
/*
다음 기능을 포함한 전화번호부를 만드시오
1. 전화번호부의 사이즈는 8로 고정합니다.
2. "add"를 입력하면 전화번호를 입력하는 창이 나오는데 "이름", "전화번호", "기타" 정보를 입력할 수 있습니다.
3. "search"를 입력하고 전화번호부의 index를 입력하면 해당 인덱스에 담겨있는 정보가 출력됩니다.
4. "exit"를 입력하면 종료됩니다.
5. 그 외 기능은 편하신대로 추가해주세요.
*/
// gcc -Wall -Wextra -g3 ../week2/FnStrcpy.c ../week2/FnMemcpy.c ../week3/FnStrcmp.c ../week3/FnStrcat.c Phone.c FSConverter.c Phonebook.c -o Phone.out
#include <string.h>
#include "PhoneBook.h"
#define QUIT "q"
#define OUTPUT_ENTER_NAME "Name (Max 15) : "
#define OUTPUT_ENTER_NUMBER "Phone Number : "
#define OUTPUT_ENTER_ETC "ETC (Max 30) : "
// Eof, exit에 구조체 할당 free
// <summary> Clean up scanf iniput buffer
void CleanBuffer() { while(getchar() != '\n'); }
void PrintMenu() {
printf(DIVIDER_LINE);
printf("1. Add new profile\n"); // Add
printf("2. Search profile\n"); // Search
printf("3. Delete Profile\n"); // Delete
printf("4. Print all profiles\n"); // Print
printf("5. Modify profile\n"); // Change
printf("6. Update profile list\n"); // Save
printf("0. Exit\n"); // Exit
}
void Execute();
void InputNumeric(char* guidText, unsigned int *number);
char* InputString(char* guidText, char* target, size_t size);
Profile* InputDefaultInformations(char *name, unsigned int *number, char *etc);
int main(void) {
Initialize();
unsigned int input = 999999;
while (1) {
PrintMenu();
// Initialize default data
char name[MAX_NAME_SIZE] = "\0";
char etc[MAX_ETC_SIZE] = "\0";
unsigned int number = 0;
// input action
if (input == 999999) {
InputNumeric("Enter your action : ", &input);
}
switch (input) {
case 1: { // Add
// Create new profile
Profile *newLog = InputDefaultInformations(name, &number, etc);
if (newLog == NULL) {
if (name[0] != 'q' && etc[0] != 'q' && number == 0) {
printf("1: ");
printf(ERROR_INPUT);
}
break;
}
switch(AddWithProfile(newLog)) {
case TaskComplete : // Success
printf("Add new profile succesfully\n");
break;
case ErrorWrongInput : // input fail
printf("2: ");
printf(ERROR_INPUT);
break;
case ErrorDuplicant : // duplicant fail
{
printf(ERROR_DUPLICANT);
char *replace = malloc(2);
InputString("Do you wish to override profile? [y/n] : ", replace, 2);
if (fn_strcmp(replace, "y") == 0) {
free(replace);
input = 5;
continue;
}
free(replace);
}
break;
case ErrorTargetEmpty : // memory allocation fail
printf(ERROR_PROFILE_EMPTY);
break;
default :
printf(ERROR_UNKNOWN);
break;
}
}
break;
case 2: { // Search
// Input name
InputString(OUTPUT_ENTER_NAME, name, MAX_NAME_SIZE);
// Search profile
Profile *search = Search(name);
if (search == NULL) {
printf(ERROR_PROFILE_MISSING);
}
else {
printf(DIVIDER_LINE_THIN);
PrintProfile(search);
}
}
break;
case 3: { // Delete
printf(DIVIDER_LINE);
printf("Enter New Information (\'quit\' to cancel)\n");
// Input name
InputString(OUTPUT_ENTER_NAME, name, MAX_NAME_SIZE);
// Break, if name equals exit char
if (fn_strcmp(name, QUIT) == 0) break;
switch(Delete(name)) {
case TaskComplete : // Success
printf("Delete succesfully\n");
break;
case ErrorDataMissing : // input fail
printf(ERROR_PROFILE_MISSING);
break;
default :
printf(ERROR_UNKNOWN);
break;
}
}
break;
case 4: { // Print
PrintAllProfiles();
}
break;
case 5: { // Replace
// Input name string if the name string is empty
if (name == NULL || name[0] == '\0') {
InputString("Target Profile Name (Max 15) : ", name, MAX_NAME_SIZE);
}
// If the name string is still empty, go to exception
if (Search(name) != NULL) {
printf(ERROR_PROFILE_MISSING);
break;
}
// Create new profile
Profile *newLog = InputDefaultInformations(name, &number, etc);
if (newLog == NULL) {
if (name[0] != 'q' && etc[0] != 'q' && number == 0) {
printf("1: ");
printf(ERROR_INPUT);
}
break;
}
// Replace profile
switch(ReplaceWithProfile(newLog)) {
case TaskComplete : // Success
printf("Repace succesfully\n");
break;
case ErrorTargetEmpty :
printf(ERROR_PROFILE_EMPTY);
break;
case ErrorTargetMissing : // input fail
printf(ERROR_PROFILE_MISSING);
break;
default :
printf(ERROR_UNKNOWN);
break;
}
}
break;
case 6: {
// Save current profile informations
SaveProfileData();
printf("Save Process Complete\n");
}
break;
case 0: { // Exit
// Save current profile informations
SaveProfileData();
MemoryFree();
printf("System Shutdown\n");
}
// Exit the process
return EXIT_SUCCESS;
default:
printf(ERROR_INPUT);
break;
}
input = 999999;
printf("\nPress Enter key to continue...\n");
CleanBuffer();
}
return EXIT_FAILURE;
}
// <summary> Input string from std input
// <parameter=*guidText> Init string to show or guide what to write
// <parameter=*target> Input taraget string
// <parameter=size> Input size
// <return> Return input target address
char* InputString(char* guidText, char* target, size_t size) {
printf("%s", guidText);
// Clean up fgets buffer
fflush(stdin);
// Input string
fgets(target, size, stdin);
// Clean up the end of the target string
size_t len = strlen(target);
if (len > 0 && target[len - 1] == '\n') {
target[len - 1] = '\0';
}
return target;
}
// <summary> Input unsigned int from std input
// <parameter=*guidText> Init string to show or guide what to write
// <parameter=*number> Input taraget unsigned int
void InputNumeric(char* guidText, unsigned int *number) {
printf("%s", guidText);
// Number input region
scanf("%8u", number);
// Clean up scanf buffer
CleanBuffer();
}
// <summary> Create new profile based on the information that given
// <parameter=*name> name string
// <parameter=*number> number int pointer
// <parameter=*etc> etc string
// <return=NULL> Return NULL pointer on fail to make new profile
// <return=Profile> Return Profile object on successfully finish the task
Profile* InputDefaultInformations(char *name, unsigned int *number, char *etc) {
printf(DIVIDER_LINE);
printf("Enter New Information (\'q\' to cancel)\n");
// Name input region
// Get new name string if name is NULL
if (name == NULL) {
char temp[MAX_NAME_SIZE];
name = InputString(OUTPUT_ENTER_NAME, temp, MAX_NAME_SIZE);
}
// Get new name string if the name is empty
else if (name[0] == '\0' || name[0] == '\n') {
InputString(OUTPUT_ENTER_NAME, name, MAX_NAME_SIZE);
}
// Print out current name
else {
printf(OUTPUT_ENTER_NAME);
printf("%s\n", name);
}
if (fn_strcmp(name, QUIT) == 0) return NULL;
// Number input region
// Print out number, if it's valid
if (*number > 99999999 && *number < 10000000) {
printf(OUTPUT_ENTER_NUMBER);
printf("%d\n", *number);
}
// Enter new number
else {
InputNumeric(OUTPUT_ENTER_NUMBER, number);
if (*number > 99999999 || *number < 10000000) *number = 0;
if (*number == 0) return NULL;
}
// Etc input region
// Get new etc string if etc is NULL
if (etc == NULL) {
char temp[MAX_ETC_SIZE];
etc = InputString(OUTPUT_ENTER_ETC, temp, MAX_ETC_SIZE);
}
// Get new etc string if the etc is empty
else if (etc[0] == '\0' || etc[0] == '\n') {
InputString(OUTPUT_ENTER_ETC, etc, MAX_ETC_SIZE);
}
// Print out current etc
else {
printf(OUTPUT_ENTER_ETC);
printf("%s\n", etc);
}
if (fn_strcmp(etc, QUIT) == 0) return NULL;
return CreateNewProfile(name, *number, etc);
}
전화번호부 코드
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "FSConverter.h"
#include "../week2/FnStrcpy.h"
#include "../week3/FnStrcat.h"
#include "../week3/FnStrcmp.h"
#define MAX_NAME_SIZE 31
#define MAX_ETC_SIZE 31
#define MAX_NUMBER_SIZE 5
#define MAX_SIZE 1000
#define DIVIDER_LINE "==============================\n"
#define DIVIDER_LINE_THIN "-----------------------\n"
#define ERROR_UNKNOWN "ERROR::Unknown error.\n"
#define ERROR_INPUT "ERROR::Incorrect input detected!\n"
#define ERROR_DATA_EMPTY "ERROR::Profile data is empty!\n"
#define ERROR_DUPLICANT "ERROR::Profile exist!\n"
#define ERROR_PROFILE_EMPTY "ERROR::Profile is empty.\n"
#define ERROR_PROFILE_MISSING "ERROR::Profile doesn't exist.\n"
#define ERROR_OUT_OF_STACK "ERROR::Cannot add more profile. Reached Maximum length.\n"
#define ERROR_MEMORY_ALLOCATION "ERROR::Memory allocation fail\n"
typedef enum _ERROR {
TaskComplete = 0,
ErrorWrongInput,
ErrorDataMissing,
ErrorDuplicant,
ErrorTargetEmpty,
ErrorTargetMissing,
ErrorOutOfStack,
ErrorMemoryAllocationFail,
} ERROR;
typedef struct PhoneBook
{
char name[MAX_NAME_SIZE];
char etc[MAX_ETC_SIZE];
unsigned int number;
} Profile;
// 해시 테이블 구조체
typedef struct HashTable {
// 해시라고 해놓고 해시가 아닌 해시 같은 너
// TO DO :
// 1. 해시 기능 구현
// 2. 해시 관련 기능 구현 (find, add, search.. etc)
Profile* table[MAX_SIZE];
} Book;
void Initialize();
Profile *CreateNewProfile(char* name, unsigned int number, char* etc);
int Add(char* name, unsigned int number, char* etc);
int AddWithProfile(Profile *profile);
int Delete(char* profile);
int Replace(char* targetName, unsigned int newNumber, char* newEtc);
int ReplaceWithProfile(Profile *profile);
Profile* Search(const char* key);
void PrintAllProfiles();
void PrintProfile(Profile *profile);
void SaveProfileData();
void MemoryFree();
#include "PhoneBook.h"
Book book;
void Initialize() {
// Initialize table
for (int k = 0; k < MAX_SIZE; k++) {
book.table[k] = NULL;
}
// Get all data from text file
char * token = strtok(FSDecoding(), ",");
char *input[3];
int k = 0;
// Splite text by "," delimiter
while (token != NULL) {
input[k++] = token;
if (k > 2) {
AddWithProfile(CreateNewProfile(input[0], atoi(input[1]), input[2]));
k = 0;
}
token = strtok(NULL, ",");
}
}
int Hash(const char* key) {
int hash = 0;
while (*key) {
hash += *key;
key++;
}
return hash % MAX_SIZE;
}
// <summary> Create new profile object
// <parameter=name> Profile name string
// <parameter=number> Profile number
// <parameter=etc> Profile etc string
// <return=Success> New Profile object
// <return=Fail> NULL
Profile *CreateNewProfile(char* name, unsigned int number, char* etc){
Profile *newLog = (Profile*)malloc(sizeof(Profile));
if (newLog == NULL) {
printf(ERROR_MEMORY_ALLOCATION);
return NULL;
}
fn_strcpy(newLog->name, name);
fn_strcpy(newLog->etc, etc);
newLog->number = number;
return newLog;
}
// <summary> Add new profile in phone book table
// <parameter=name> Profile name string
// <parameter=number> Profile number
// <parameter=etc> Profile etc string
// <return=0> Task Success
// <return=1> Invalid input value
// <return=2> Profile already exist
// <return=3> Fail to create new profile object
int Add(char* name, unsigned int number, char* etc) {
if (name[0] == '\0' || (number > 99999999 || number < 10000000)) {
return ErrorWrongInput;
}
if (Search(name) != NULL) {
return ErrorDuplicant;
}
Profile *newLog = CreateNewProfile(name, number, etc);
if(newLog == NULL) {
return ErrorTargetMissing;
}
book.table[Hash(name)] = newLog;
return TaskComplete;
}
// <summary> Add new profile in phone book table
// <parameter=profile> New Profile object
// <return=0> Task Success
// <return=1> Invalid input value
// <return=2> Profile already exist
// <return=3> Fail to create new profile object
int AddWithProfile(Profile *profile) {
if (profile == NULL) {
return ErrorTargetEmpty;
}
if (profile->name[0] == '\0' || (profile->number > 99999999 || profile->number < 10000000)) {
return ErrorWrongInput;
}
if (Search(profile->name) != NULL) {
return ErrorDuplicant;
}
book.table[Hash(profile->name)] = profile;
return TaskComplete;
}
// <summary> Delete exist profile in phone book table
// <parameter=profile> New Profile object
// <return=0> TaskComplete
// <return=2> ErrorDataMissing
int Delete(char* profile) {
if (Search(profile) == NULL)
return ErrorDataMissing;
Profile* temp = book.table[Hash(profile)];
book.table[Hash(profile)] = NULL;
free(temp);
return TaskComplete;
}
// <summary> Replace exist profile in phone book table
// <parameter=*targetName> Target Profile name
// <parameter=*newNumber> Target Profile new number
// <parameter=*newEtc> Target Profile new etc
// <return=0> TaskComplete
// <return=5> ErrorTargetMissing
int Replace(char* targetName, unsigned int newNumber, char* newEtc) {
Profile *replacer = Search(targetName);
if (replacer == NULL)
return ErrorTargetMissing;
replacer->number = newNumber;
*replacer->etc = *newEtc;
return TaskComplete;
}
// <summary> Replace exist profile in phone book table
// <parameter=*profile> New Profile object
// <return=0> TaskComplete
// <return=4> ErrorTargetEmpty
// <return=5> ErrorTargetMissing
int ReplaceWithProfile(Profile *profile) {
if (profile == NULL) {
return ErrorTargetEmpty;
}
Profile *replacer = Search(profile->name);
if (replacer == NULL){
return ErrorTargetMissing;
}
replacer->number = profile->number;
fn_strcpy(replacer->etc, profile->etc);
free(profile);
return TaskComplete;
}
// <summary> Search exist profile in phone book table
// <parameter=*key> Profile name
// <return=NULL> Return NULL on fail to find target
// <return=Profile> Return profile object
Profile* Search(const char* key) {
if(book.table[Hash(key)] == NULL) {
return NULL;
}
Profile* current = book.table[Hash(key)];
if (fn_strcmp(current->name, key) != 0) {
return NULL;
}
return current;
}
// <summary> Print out all profiles
void PrintAllProfiles() {
int count = 0;
for (int k = 0; k < MAX_SIZE; k++) {
if (book.table[k] != NULL) {
count++;
printf(DIVIDER_LINE_THIN);
PrintProfile(book.table[k]);
}
}
printf(DIVIDER_LINE_THIN);
printf("Totall : %d\n", count);
}
// <summary> Print out profile properties
void PrintProfile(Profile *profile) {
printf("Name : %s\n", profile->name);
printf("Number : %u\n", profile->number);
printf("ETC : %s\n", profile->etc);
}
// <summary> Save all profile informations into file system
void SaveProfileData() {
char *saveData = (char*)malloc(1);
saveData[0] = '\0';
for (int k = 0; k < MAX_SIZE; k++) {
if (book.table[k] != NULL) {
int nameSize = strlen(book.table[k]->name);
int etcSize = strlen(book.table[k]->etc);
char info[(nameSize + etcSize + sizeof(long long) + 6)];
// Copy name
fn_strcpy(info, book.table[k]->name);
//strcpy(info, book.table[k]->name);
fn_strcat(info, ",");
// Cat number
// Need sprintf to convert int to char*
char num[16];
sprintf(num, "%u", book.table[k]->number);
fn_strcat(info, num);
fn_strcat(info, ",");
// Cat etc
// If etc is empty, add space
// If not strtok will think it's the end of the string
if (etcSize == 0)
fn_strcat(info, "\x20");
else
fn_strcat(info, book.table[k]->etc);
fn_strcat(info, ",");
// Save the process
fn_strcat(saveData, info);
}
}
fn_strcat(saveData, "\0");
FSIncoding(saveData);
}
void MemoryFree() {
for (int k = 0; k < MAX_SIZE; k++) {
free(book.table[k]);
}
}
파일 입출력 코드
#include "FSConverter.h"
void CheckForFile() {
// Check the file and create new one if it doesn't exist
FILE *afs = fopen("PhoneBookLog.txt", "a");
fclose(afs);
}
void FSIncoding (char* input) {
// Open the file to write
// Create new one if there's no file
FILE *fs = fopen("PhoneBookLog.txt", "w");
if (fs == NULL)
{
perror(ERROR_FS_OPEN);
fclose(fs);
return;
}
// Calcualte length of the text
fprintf(fs, "%s", input);
// close the file
fclose(fs);
}
char *FSDecoding () {
CheckForFile();
// Open the file to read
FILE *fs = fopen("PhoneBookLog.txt", "r");
if (fs == NULL)
{
perror(ERROR_FS_OPEN);
fclose(fs);
return NULL;
}
// 파일 끝으로 이동하여 파일 크기 확인
// Move to end of the file to Check size of the file
fseek(fs, 0, SEEK_END);
long fileSize = ftell(fs);
// 파일 읽기 위치를 다시 파일의 시작으로 되돌림
// Reset read position to start of the file
rewind(fs);
// Buffer
char *buffer = (char*)malloc(fileSize + 1);
if (buffer == NULL) {
printf("ERROR::Memorry allocation fail\n");
fclose(fs);
return NULL;
}
// 파일 내용을 버퍼로 읽음
// Read all file
size_t bytesRead = fread(buffer, 1, fileSize, fs);
// 문자열 종료 문자 추가
buffer[bytesRead] = '\0';
// close the file
fclose(fs);
return buffer;
}