สองประเด็นที่อยู่รอบ ๆ คำถามนี้คือการจัดการหน่วยความจำและความปลอดภัยของเธรด อย่างที่คุณเห็นจากโพสต์ต่าง ๆ นี่ไม่ใช่งานง่ายที่จะทำให้สำเร็จอย่างราบรื่นในซีฉันต้องการโซลูชันที่:
- ด้ายปลอดภัย (strtok ไม่ปลอดภัยสำหรับเธรด)
- ไม่ใช้ malloc หรืออนุพันธ์ใด ๆ (เพื่อหลีกเลี่ยงปัญหาการจัดการหน่วยความจำ)
- ตรวจสอบขอบเขตของอาเรย์บนแต่ละฟิลด์ (เพื่อหลีกเลี่ยงข้อผิดพลาดของเซ็กเมนต์ในข้อมูลที่ไม่รู้จัก)
- ทำงานร่วมกับตัวแยกฟิลด์แบบหลายไบต์ (utf-8)
- ละเว้นฟิลด์พิเศษในอินพุต
- จัดเตรียมรูทีนข้อผิดพลาดแบบ soft สำหรับความยาวฟิลด์ที่ไม่ถูกต้อง
วิธีการแก้ปัญหาที่ฉันเกิดขึ้นตรงตามเกณฑ์เหล่านี้ทั้งหมด อาจเป็นงานที่ต้องติดตั้งอีกเล็กน้อยกว่าโซลูชันอื่น ๆ ที่โพสต์ไว้ที่นี่ แต่ฉันคิดว่าในทางปฏิบัติงานพิเศษนั้นคุ้มค่าเพื่อหลีกเลี่ยงข้อผิดพลาดทั่วไปของโซลูชันอื่น ๆ
#include <stdio.h>
#include <string.h>
struct splitFieldType {
char *field;
int maxLength;
};
typedef struct splitFieldType splitField;
int strsplit(splitField *fields, int expected, const char *input, const char *fieldSeparator, void (*softError)(int fieldNumber,int expected,int actual)) {
int i;
int fieldSeparatorLen=strlen(fieldSeparator);
const char *tNext, *tLast=input;
for (i=0; i<expected && (tNext=strstr(tLast, fieldSeparator))!=NULL; ++i) {
int len=tNext-tLast;
if (len>=fields[i].maxLength) {
softError(i,fields[i].maxLength-1,len);
len=fields[i].maxLength-1;
}
fields[i].field[len]=0;
strncpy(fields[i].field,tLast,len);
tLast=tNext+fieldSeparatorLen;
}
if (i<expected) {
if (strlen(tLast)>fields[i].maxLength) {
softError(i,fields[i].maxLength,strlen(tLast));
} else {
strcpy(fields[i].field,tLast);
}
return i+1;
} else {
return i;
}
}
void monthSplitSoftError(int fieldNumber, int expected, int actual) {
fprintf(stderr,"monthSplit: input field #%d is %d bytes, expected %d bytes\n",fieldNumber+1,actual,expected);
}
int main() {
const char *fieldSeparator=",";
const char *input="JAN,FEB,MAR,APRI,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC,FOO,BAR";
struct monthFieldsType {
char field1[4];
char field2[4];
char field3[4];
char field4[4];
char field5[4];
char field6[4];
char field7[4];
char field8[4];
char field9[4];
char field10[4];
char field11[4];
char field12[4];
} monthFields;
splitField inputFields[12] = {
{monthFields.field1, sizeof(monthFields.field1)},
{monthFields.field2, sizeof(monthFields.field2)},
{monthFields.field3, sizeof(monthFields.field3)},
{monthFields.field4, sizeof(monthFields.field4)},
{monthFields.field5, sizeof(monthFields.field5)},
{monthFields.field6, sizeof(monthFields.field6)},
{monthFields.field7, sizeof(monthFields.field7)},
{monthFields.field8, sizeof(monthFields.field8)},
{monthFields.field9, sizeof(monthFields.field9)},
{monthFields.field10, sizeof(monthFields.field10)},
{monthFields.field11, sizeof(monthFields.field11)},
{monthFields.field12, sizeof(monthFields.field12)}
};
int expected=sizeof(inputFields)/sizeof(splitField);
printf("input data: %s\n", input);
printf("expecting %d fields\n",expected);
int ct=strsplit(inputFields, expected, input, fieldSeparator, monthSplitSoftError);
if (ct!=expected) {
printf("string split %d fields, expected %d\n", ct,expected);
}
for (int i=0;i<expected;++i) {
printf("field %d: %s\n",i+1,inputFields[i].field);
}
printf("\n");
printf("Direct structure access, field 10: %s", monthFields.field10);
}
ด้านล่างนี้เป็นตัวอย่างการคอมไพล์และเอาท์พุท โปรดทราบว่าในตัวอย่างของฉันฉันสะกดคำว่า "APRIL" อย่างมีจุดประสงค์เพื่อให้คุณสามารถดูว่าข้อผิดพลาดแบบนุ่มนวลทำงานอย่างไร
$ gcc strsplitExample.c && ./a.out
input data: JAN,FEB,MAR,APRIL,MAY,JUN,JUL,AUG,SEP,OCT,NOV,DEC,FOO,BAR
expecting 12 fields
monthSplit: input field #4 is 5 bytes, expected 3 bytes
field 1: JAN
field 2: FEB
field 3: MAR
field 4: APR
field 5: MAY
field 6: JUN
field 7: JUL
field 8: AUG
field 9: SEP
field 10: OCT
field 11: NOV
field 12: DEC
Direct structure access, field 10: OCT
สนุก!
strtok
ฟังก์ชันจากไลบรารีมาตรฐานเพื่อให้ได้สิ่งเดียวกัน