[英]How does it make sense and why is the sscanf function still working?
正如你所看到的,我只在循環內部分配了1個字節作為sizeof(char)
,而sscanf()
仍然讀取整個塊,直到空白為string_of_letters
。 這怎么可能?
sscanf()
的定義是什么?
例如: str = "rony is a man"
,但在string_of_letters
位置i
我看“羅尼”。
char **string_of_letters;
int i;
char *read = str;
string_of_letters = (char**)malloc(3 * sizeof(char*));
for (i = 0; i < 3; i++) {
string_of_letters[i] = (char*)malloc(sizeof(char));
sscanf(read,"%[^, ]", &(*string_of_letters[i]));
printf("%s\n", string_of_letters[i]);
}
C不會強制執行運行時內存邊界檢查,因此只分配一個字節這一事實對sscanf
的功能沒有影響:它會愉快地嘗試將整個字符串存儲到您提供的指針所指向的內存位置。 如果緩沖區不夠大,結果是未定義的行為,其確切后果取決於要考慮的太多因素(使用的編譯器及其版本,操作系統,當前的內存狀態等)。
在像你這樣的小玩具程序中,它似乎工作正常並不奇怪,因為緩沖區足夠小並且沒有其他的東西在進行。 然而,在一個更大的程序中, sscanf
可能會在傳入緩沖區的末尾寫入另一個緩沖區,分配給其他內容,改變你不想改變的內存,或者,如果你很幸運,例如進入受保護的內存,導致訪問沖突。
有很多方法可以修復顯示的代碼片段。 此代碼顯示其中三個。 正如在問題的評論中所指出的,你需要在循環中分配至少2個字符(因為%[…]
掃描集創建一個以空字符結尾的字符串),但是你可以使用%1[^, ]
作為轉換一次得到一個角色。 請注意,您需要測試sscanf()
的返回值,以檢查您是否得到了預期。 您還需要增加讀取,以便不會反復讀取相同的字符。 在更一般的情況下,您將使用%n
來告知掃描停止的位置(請參閱在循環中使用sscanf()
)。 掃描集不會跳過空格( %c
或%n
也不會跳過 - 所有其他標准轉換都會跳過前導空格,包括換行符)。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
enum { LIST_SIZE = 3 };
static void free_array(size_t n, char **arr)
{
for (size_t i = 0; i < n; i++)
free(arr[i]);
free(arr);
}
int main(void)
{
char str[] = "rony is a man";
char **string_of_letters;
char *read = str;
printf("Variant 1:\n");
string_of_letters = (char **)malloc(LIST_SIZE * sizeof(char *));
for (int i = 0; i < LIST_SIZE; i++)
{
string_of_letters[i] = (char *)malloc(2 * sizeof(char));
if (sscanf(&read[i], "%1[^, ]", string_of_letters[i]) != 1)
printf("Conversion failed on %d\n", i);
else
printf("%s\n", string_of_letters[i]);
}
free_array(LIST_SIZE, string_of_letters);
printf("Variant 2:\n");
string_of_letters = (char **)malloc(LIST_SIZE * sizeof(char *));
for (int i = 0; i < LIST_SIZE; i++)
{
string_of_letters[i] = (char *)malloc(sizeof(char));
*string_of_letters[i] = read[i];
printf("%c\n", *string_of_letters[i]);
}
free_array(LIST_SIZE, string_of_letters);
printf("Variant 3:\n");
strcpy(str, " r o n");
char char_list[LIST_SIZE + 1]; // NB: + 1 provides space for null byte
int offset = 0;
for (int i = 0; i < LIST_SIZE; i++)
{
int pos;
printf("Offset = %d: ", offset);
if (sscanf(&read[offset], " %1[^, ]%n", &char_list[i], &pos) != 1)
{
printf("Conversion failed on character index %d\n", i);
break;
}
else
printf("%c\n", char_list[i]);
offset += pos;
}
return 0;
}
顯示的代碼在運行macOS 10.13.6 High Sierra的Mac上的Valgrind下運行得很干凈,使用Valgrind 3.14.0.GIT(從Git中提取的版本,而不是正式發布的源代碼集)。
輸出:
Variant 1:
r
o
n
Variant 2:
r
o
n
Variant 3:
Offset = 0: r
Offset = 3: o
Offset = 5: n
正如已經觀察到的那樣,問題分類中的代碼更有效,而不是設計。 malloc()
返回的指針受到約束,因此它指向可用於任何目的的內存位置:
¶1...如果分配成功,則返回的指針被適當地對齊,以便可以將其指定給具有基本對齊要求的任何類型對象的指針,然后用於在分配的空間中訪問此類對象或此類對象的數組(......)。 ...
這意味着由於其他類型的對齊要求,單個char
連續分配將不是連續的。 通常,您會發現分配的最小空間為8或16個字節(在32位或64位平台上),但這絕不是必需的。 這意味着通常會分配比您請求的空間更多的空間(特別是如果您請求單個字節)。 但是,訪問該額外空間會導致未定義的行為。 您運行的示例代碼顯示有時“未定義的行為”的行為或多或少與預期的一致。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.