2010-10-06 9 views
21

Các chức năng thư viện C chuẩn strtofstrtod có chữ ký sau đây:Tại sao tham số endptr để strtof và strtod một con trỏ đến một con trỏ char không const?

float strtof(const char *str, char **endptr); 
double strtod(const char *str, char **endptr); 

Họ từng phân hủy chuỗi đầu vào, str, thành ba phần:

  1. Một ban đầu, có thể có sản phẩm nào, chuỗi các khoảng trắng
  2. Một "chuỗi chủ đề" của các ký tự đại diện cho một giá trị dấu phẩy động
  3. Một "chuỗi cuối" của các ký tự không có giá trị được công nhận (và không ảnh hưởng đến chuyển đổi).

Nếu endptr không phải là NULL, sau đó *endptr được đặt thành một con trỏ đến các nhân vật ngay sau khi ký tự cuối cùng đó là một phần của việc chuyển đổi (nói cách khác, sự bắt đầu của chuỗi dấu).

tôi tự hỏi: tại sao là endptr, sau đó, một con trỏ đến một phi constchar con trỏ? Không phải là *endptr một con trỏ vào một số constchar chuỗi (chuỗi đầu vào str)?

+0

Về cơ bản cũng giống như 'strchr' và bạn bè, ngoại trừ ở đây chúng ta có con trỏ out-param thay vì giá trị trả về. –

+0

@Steve: vâng, nhưng nó có vấn đề hơn 'strchr' bởi vì bạn không thể chuyển địa chỉ của con trỏ' const' đủ điều kiện cho các hàm này mà không có một diễn viên rõ ràng. –

+3

Câu hỏi thú vị. Về cơ bản, điều này có nghĩa là bạn có thể ẩn một diễn viên từ 'char const *' thành 'char *' đằng sau các hàm 'strtoX'. Kỳ dị. –

Trả lời

10

Lý do chỉ đơn giản là khả năng sử dụng. char * có thể tự động chuyển đổi thành const char *, nhưng char ** không thể tự động chuyển đổi thành const char ** và loại thực tế của con trỏ (địa chỉ được truyền) được sử dụng bởi chức năng gọi có nhiều khả năng là char * hơn const char *. Lý do không thể chuyển đổi tự động này là có một cách không rõ ràng, nó có thể được sử dụng để loại bỏ chứng chỉ const qua một vài bước, trong đó mỗi bước trông hoàn toàn hợp lệ và chính xác trong và chính nó. Steve Jessop đã cung cấp một ví dụ trong các ý kiến:

nếu bạn có thể tự động chuyển đổi char**-const char**, sau đó bạn có thể làm

char *p; 
char **pp = &p; 
const char** cp = pp; 
*cp = (const char*) "hello"; 
*p = 'j';. 

Đối const-an toàn, một trong những dòng phải là bất hợp pháp, và kể từ khi những người khác đều hoạt động hoàn toàn bình thường, nó phải được cp = pp;

Một cách tiếp cận tốt hơn sẽ có được để xác định các chức năng này để đưa void * thay cho char **. Cả hai char **const char ** có thể tự động chuyển đổi thành void *. (Các văn bản bị ảnh hưởng thực sự là một ý tưởng rất xấu, không chỉ ngăn chặn bất kỳ loại kiểm tra, nhưng C thực sự cấm các đối tượng của loại char *const char * để bí danh.) Ngoài ra, các chức năng này có thể lấy một đối số ptrdiff_t * hoặc size_t * lưu trữ các bù đắp của kết thúc, chứ không phải là một con trỏ đến nó. Điều này thường hữu ích hơn.

Nếu bạn thích cách tiếp cận thứ hai, vui lòng viết trình bao bọc xung quanh các chức năng thư viện chuẩn và gọi trình bao bọc của bạn để giữ phần còn lại của mã số const -clean và không có cast.

+2

"Cả hai' char ** 'và' const char ** 'có thể tự động chuyển thành void *." Tôi thấy những gì bạn đang nói, và sử dụng 'void *' sẽ cho phép người dùng truyền vào một 'const char **' mà không có một diễn viên. Nhưng nó cũng sẽ cho phép họ vượt qua trong một vũ trụ toàn bộ những điều sai trái mà không có một diễn viên. Với những hạn chế của C, tôi nghĩ tốt hơn là bảo toàn kiểu an toàn cơ bản, ngay cả chi phí mất an toàn const. –

+0

Nó không chỉ đơn giản là khả năng sử dụng, các chức năng này lưu trữ kết quả trong con trỏ đó, và bạn không thể ghi vào bộ nhớ không đổi. –

+0

@Vlad: Bạn đang bối rối 'const char **' với 'char * const *'. –

4

Tính khả dụng. Đối số str được đánh dấu là const vì đối số đầu vào sẽ không bị sửa đổi. Nếu endptrconst, thì điều đó sẽ hướng dẫn người gọi rằng anh ấy không nên thay đổi dữ liệu được tham chiếu từ endptr trên đầu ra, nhưng thường người gọi muốn thực hiện điều đó. Ví dụ: tôi có thể muốn hủy bỏ một chuỗi sau khi nhận được dấu phẩy ra khỏi nó:

float StrToFAndTerminate(char *Text) { 
    float Num; 

    Num = strtof(Text, &Text); 
    *Text = '\0'; 
    return Num; 
} 

Điều hoàn toàn hợp lý muốn làm trong một số trường hợp. Không hoạt động nếu endptr thuộc loại const char **.

Lý tưởng nhất, endptr phải là của const-ness khớp với thành phần đầu vào thực tế của str, nhưng C không có cách nào chỉ ra điều này thông qua cú pháp của nó. (Anders Hejlsberg talks about this khi mô tả lý do tại sao const bị bỏ qua khỏi C#.)

+0

Cùng một hiệu ứng có thể dễ dàng đạt được với 'Văn bản [FloatEnd-Text] = '\ 0'' vì vậy đối với tôi điều này không thực sự là một cái cớ tốt. Phương thức mà bạn đang chỉ ra sẽ tạo ra UB nếu 'Văn bản' sẽ được khai báo bằng' const' và bạn không thể dễ dàng đọc nó từ vị trí mà bạn thực hiện nhiệm vụ. –

+0

@Jens: Tôi đã cập nhật mã để phản ánh tốt hơn lý do. Và tôi đồng ý về vấn đề hành vi không xác định. Câu hỏi đặt ra là liệu việc chữa bệnh có tệ hơn bệnh ... –

+0

"có thể dễ dàng đạt được" - nhưng tối ưu hóa sớm cho chúng ta (thích trực tiếp con trỏ, thay vì dựa vào trình biên dịch để phân loại biểu thức đó và/hoặc phần cứng nhanh đến mức chúng tôi không quan tâm), không được tối ưu hóa sớm cho ủy ban tiêu chuẩn C89 khi họ chỉ định 'strtod'. Ngoài ra, đó là một thành ngữ đáng xấu hổ phải học, vì vậy chúng tôi chỉ còn lại tự hỏi cho dù đó là nhiều hơn hoặc ít đáng xấu hổ hơn const-không đúng ;-) –