[英]Storing a string in char* in C
在下面的代碼中,希望您可以看到我有一個char*
變量,並且希望從文件中讀取字符串。 然后,我想將此字符串從函數中傳回。 我對指針感到困惑,因此我不太確定我應該真正做什么。
目的是將數組傳遞給另一個函數以搜索名稱。
不幸的是,程序因此崩潰了,我也不知道為什么。
char* ObtainName(FILE *fp)
{
char* temp;
int i = 0;
temp = fgetc(fp);
while(temp != '\n')
{
temp = fgetc(fp);
i++;
}
printf("%s", temp);
return temp;
}
任何幫助將不勝感激。
fgetc
返回一個int
,而不是char*
。 此int
是流中的字符,如果到達文件末尾,則為EOF
。
您將int
隱式地轉換為char*
,即,將其解釋為地址(打開警告。)當您調用printf
它將讀取該地址並一次繼續讀取一個字符以尋找結尾的空終止符字符串,但是該地址幾乎肯定是無效的。 這是未定義的行為。
我對您要完成的工作持自由態度。 而是處理指針,只要您可以設置最大長度,就可以使用固定大小的數組。 我還包括了幾項檢查,以便您不會用完緩沖區末尾或文件末尾。 同樣重要的是要確保在字符串的末尾具有空終止符'\\ 0'。
#define MAX_LEN 100
char* ObtainName(FILE *fp)
{
static char temp[MAX_LEN];
int i = 0;
while(i < MAX_LEN-1)
{
if (feof(fp))
{
break;
}
temp[i] = fgetc(fp);
if (temp[i] == '\n')
{
break;
}
i++;
}
temp[i] = '\0';
printf("%s", temp);
return temp;
}
因此,這里有幾個問題:
1.您沒有為字符串內容預留存儲空間
線
char *temp;
聲明temp
為char
的指針; 它的值將是單個字符值的地址 。 由於它是在本地范圍內聲明的,而沒有static
關鍵字,因此其初始值將是不確定的,並且該值可能與有效的內存地址不對應。
它不會為從fp
讀取的字符串內容預留任何存儲空間; 那將必須作為一個單獨的步驟完成,我將在下面進行介紹。
2.您沒有正確存儲字符串內容
線
temp = fgetc(fp);
從fp
讀取下一個字符,並將其分配給temp
。 首先,這意味着您只存儲從流中讀取的最后一個字符,而不是整個字符串。 其次,更重要的是,您將fgetc()
的結果(返回的值是int
類型的值fgetc()
分配給char *
類型的對象(被視為地址)。 您基本上是在說:“我想將字母'a'當作內存中的地址。” 這將我們帶到...
3.您正在嘗試讀取不屬於您的內存
在行中
printf("%s", temp);
您正在嘗試從存儲在temp
中的地址開始打印字符串。 由於您最后寫給temp
東西很可能是一個字符,其值<127,因此您要告訴printf
從一個非常低且很可能無法訪問的地址開始,從而導致崩潰。
4.保證您返回字符串的方式一定會讓您心痛
由於您已經定義了返回char *
的函數,因此您需要執行以下操作之一:
static
關鍵字聲明一個數組,以使函數退出后該數組不會“消失”。 但是,這種方法有嚴重的缺陷。 動態分配內存
您可以使用動態內存分配例程為字符串內容留出一個存儲區域,如下所示:
char *temp = malloc( MAX_STRING_LENGTH * sizeof *temp );
要么
char *temp = calloc( MAX_STRING_LENGTH, sizeof *temp );
然后返回您編寫的temp
。
malloc
和calloc
保留了您指定的字節數。 calloc
會將所有這些字節初始化為0,這會花費更多時間,但是可以節省您的培根,尤其是在處理文本時。
問題是,當不再需要該內存時,必須有人重新分配該內存。 因為返回了指針,所以調用此函數的人現在都有責任在處理完該字符串后調用free()
,例如:
void Caller( FILE *fp )
{
...
char *name = ObtainName( fo );
...
free( name );
...
}
這將程序的內存管理責任分散到整個程序中,從而增加了有人忘記釋放該內存的可能性,從而導致內存泄漏。 理想情況下 ,您希望具有與釋放內存相同的功能。
使用靜態數組
您可以將temp
聲明為char
數組 ,並使用static
關鍵字:
static char temp[MAX_STRING_SIZE];
程序啟動時,這將在數組中預留MAX_STRING_SIZE
字符,並且將在ObtainName
調用ObtainName
之間保留該ObtainName
。 完成后,無需打free
電話。
這種方法的問題是,通過創建靜態緩沖區,代碼無法重入 ; 如果ObtainName
調用了另一個函數,而該函數又又調用了ObtainName
,則該新調用將破壞緩沖區中的內容。
為什么不將temp
聲明為
char temp[MAX_STRING_SIZE];
沒有static
關鍵字? 問題在於,當ObtainName
退出時, temp
數組將不復存在(或者,它正在使用的內存可供其他人使用)。 您返回的指針不再有效,並且數組的內容可能會被覆蓋,然后才能再次訪問它。
更改功能定義
理想情況下,您希望ObtainName
不必擔心它必須寫入的內存。 實現此目的的最佳方法是調用方將目標緩沖區作為參數以及緩沖區的大小傳遞:
int ObtainName( FILE *fp, char *buffer, size_t bufferSize )
{
...
}
這樣, ObtainName
會將數據寫入調用者指定的位置(如果要出於不同目的而獲取多個名稱,則很有用)。 該函數將返回一個整數值,該整數值可以是簡單的成功或失敗,也可以是指示錯誤原因的錯誤代碼,等等。
注意,如果您正在閱讀文本,則不必逐個字符地閱讀。 您可以使用fgets()
或fscanf()
類的函數一次讀取整個字符串。
如果要讀取空格分隔的字符串,請使用fscanf
(即,如果輸入文件包含"This is a test"
, fscanf( fp, "%s", temp);
將僅讀取"This"
)。 如果要讀取整行(由換行符分隔),請使用fgets()
。
假設您想一次讀取一個單獨的字符串,則可以使用以下內容(假設為C99):
#define FMT_SIZE 20
...
int ObtainName( FILE *fp, char *buffer, size_t bufsize )
{
int result = 1; // assume success
int scanfResult = 0;
char fmt[FMT_SIZE];
sprintf( fmt, "%%%zus", bufsize - 1 );
scanfResult = fscanf( fp, fmt, buffer );
if ( scanfResult == EOF )
{
// hit end-of-file before reading any text
result = 0;
}
else if ( scanfResult == 0 )
{
// did not read anything from input stream
result = 0;
}
else
{
result = 1;
}
return result;
}
那是什么聲音
char fmt[FMT_SIZE];
sprintf( fmt, "%%%zus", bufsize - 1 );
關於? 當使用%s
或%[
轉換說明符而沒有最大長度說明符時, fscanf()
存在一個非常討厭的安全漏洞。 %s
轉換說明符告訴fscanf
讀取字符,直到看到空白字符為止。 如果流中非空白字符的數量超過緩沖區可容納的大小,則fscanf
將在緩沖區末尾存儲這些多余的字符,從而破壞其后的所有內存。 這是一種常見的惡意軟件利用。 因此,我們要為輸入指定最大長度; 例如, %20s
表示要從流中讀取不超過20個字符並將它們存儲到緩沖區中。
不幸的是,由於緩沖區長度是作為參數傳遞的,因此我們無法編寫類似%20s
,並且fscanf
不能像fprintf
那樣為我們指定長度作為參數。 因此,我們必須創建一個單獨的格式字符串,並將其存儲在fmt
。 如果輸入緩沖區長度為10,則格式字符串將為%10s
。 如果輸入緩沖區長度為1000,則格式字符串將為%1000s
。
以下代碼擴展了您的問題,並返回分配的存儲中的字符串:
char* ObtainName(FILE *fp)
{
int temp;
int i = 1;
char *string = malloc(i);
if(NULL == string)
{
fprintf(stderr, "malloc() failed\n");
goto CLEANUP;
}
*string = '\0';
temp = fgetc(fp);
while(temp != '\n')
{
char *newMem;
++i;
newMem=realloc(string, i);
if(NULL==newMem)
{
fprintf(stderr, "realloc() failed.\n");
goto CLEANUP;
}
string=newMem;
string[i-1] = temp;
string[i] = '\0';
temp = fgetc(fp);
}
CLEANUP:
printf("%s", string);
return(string);
}
請小心“ free()”此函數返回的字符串,否則會發生內存泄漏。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.