gcc-10.0.1 Segfault cụ thể


23

Tôi có một gói R với mã được biên dịch C tương đối ổn định trong một thời gian và thường được thử nghiệm trên nhiều nền tảng và trình biên dịch (windows / osx / debian / fedora gcc / clang).

Gần đây, một nền tảng mới đã được thêm vào để kiểm tra lại gói:

Logs from checks with gcc trunk aka 10.0.1 compiled from source
on Fedora 30. (For some archived packages, 10.0.0.)

x86_64 Fedora 30 Linux

FFLAGS="-g -O2 -mtune=native -Wall -fallow-argument-mismatch"
CFLAGS="-g -O2 -Wall -pedantic -mtune=native -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong -fstack-clash-protection -fcf-protection"
CXXFLAGS="-g -O2 -Wall -pedantic -mtune=native -Wno-ignored-attributes -Wno-deprecated-declarations -Wno-parentheses -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong -fstack-clash-protection -fcf-protection"

Tại thời điểm đó, mã được biên dịch nhanh chóng bắt đầu segfaulting dọc theo các dòng này:

 *** caught segfault ***
address 0x1d00000001, cause 'memory not mapped'

Tôi đã có thể tái tạo segfault một cách nhất quán bằng cách sử dụng bộ rocker/r-basechứa docker gcc-10.0.1với mức tối ưu hóa -O2. Chạy tối ưu hóa thấp hơn được thoát khỏi vấn đề. Chạy bất kỳ thiết lập nào khác, bao gồm dưới valgrind (cả -O0 và -O2), UBSAN (gcc / clang), cho thấy không có vấn đề gì cả. Tôi cũng chắc chắn rằng điều này chạy theo gcc-10.0.0, nhưng không có dữ liệu.

Tôi đã chạy gcc-10.0.1 -O2phiên bản với gdbvà nhận thấy một cái gì đó có vẻ kỳ lạ đối với tôi:

mã gdb vs mã

Trong khi bước qua phần được tô sáng, có vẻ như việc khởi tạo các phần tử thứ hai của các mảng bị bỏ qua ( R_alloclà một trình bao bọc xung quanh mallocđó rác tự thu thập khi trả lại quyền điều khiển cho R; segfault xảy ra trước khi trở về R). Sau đó, chương trình gặp sự cố khi phần tử chưa khởi tạo (trong phiên bản gcc.10.0.1 -O2) được truy cập.

Tôi đã sửa lỗi này bằng cách khởi tạo rõ ràng phần tử được đề cập ở mọi nơi trong mã mà cuối cùng dẫn đến việc sử dụng phần tử, nhưng nó thực sự đã được khởi tạo thành một chuỗi trống, hoặc ít nhất đó là những gì tôi sẽ giả định.

Tôi đang thiếu một cái gì đó rõ ràng hoặc làm điều gì đó ngu ngốc? Cả hai đều có khả năng hợp lý vì C là ngôn ngữ thứ hai của tôi cho đến nay . Thật kỳ lạ khi điều này vừa được cắt ra và tôi không thể hiểu trình biên dịch đang cố gắng làm gì.


CẬP NHẬT : Hướng dẫn sao chép này, mặc dù điều này sẽ chỉ sao chép miễn là debian:testingcontainer docker có gcc-10tại gcc-10.0.1. Ngoài ra, đừng chỉ chạy các lệnh này nếu bạn không tin tưởng tôi .

Xin lỗi đây không phải là một ví dụ tái sản xuất tối thiểu.

docker pull rocker/r-base
docker run --rm -ti --security-opt seccomp=unconfined \
  rocker/r-base /bin/bash
apt-get update
apt-get install gcc-10 gdb
gcc-10 --version  # confirm 10.0.1
# gcc-10 (Debian 10-20200222-1) 10.0.1 20200222 (experimental) 
# [master revision 01af7e0a0c2:487fe13f218:e99b18cf7101f205bfdd9f0f29ed51caaec52779]

mkdir ~/.R
touch ~/.R/Makevars
echo "CC = gcc-10
CFLAGS = -g -O2 -Wall -pedantic -mtune=native -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong -fstack-clash-protection -fcf-protection
" >> ~/.R/Makevars

R -d gdb --vanilla

Sau đó, trong R console, sau khi gõ runđể có được gdbđể chạy các chương trình:

f.dl <- tempfile()
f.uz <- tempfile()

github.url <- 'https://github.com/brodieG/vetr/archive/v0.2.8.zip'

download.file(github.url, f.dl)
unzip(f.dl, exdir=f.uz)
install.packages(
  file.path(f.uz, 'vetr-0.2.8'), repos=NULL,
  INSTALL_opts="--install-tests", type='source'
)
# minimal set of commands to segfault
library(vetr)
alike(pairlist(a=1, b="character"), pairlist(a=1, b=letters))
alike(pairlist(1, "character"), pairlist(1, letters))
alike(NULL, 1:3)                  # not a wild card at top level
alike(list(NULL), list(1:3))      # but yes when nested
alike(list(NULL, NULL), list(list(list(1, 2, 3)), 1:25))
alike(list(NULL), list(1, 2))
alike(list(), list(1, 2))
alike(matrix(integer(), ncol=7), matrix(1:21, nrow=3))
alike(matrix(character(), nrow=3), matrix(1:21, nrow=3))
alike(
  matrix(integer(), ncol=3, dimnames=list(NULL, c("R", "G", "B"))),
  matrix(1:21, ncol=3, dimnames=list(NULL, c("R", "G", "B")))
)

# Adding tests from docs

mx.tpl <- matrix(
  integer(), ncol=3, dimnames=list(row.id=NULL, c("R", "G", "B"))
)
mx.cur <- matrix(
  sample(0:255, 12), ncol=3, dimnames=list(row.id=1:4, rgb=c("R", "G", "B"))
)
mx.cur2 <-
  matrix(sample(0:255, 12), ncol=3, dimnames=list(1:4, c("R", "G", "B")))

alike(mx.tpl, mx.cur2)

Kiểm tra trong gdb khá nhanh cho thấy (nếu tôi hiểu chính xác) CSR_strmlen_xđang cố truy cập chuỗi không được khởi tạo.

CẬP NHẬT 2 : đây là một hàm đệ quy cao và trên hết, bit khởi tạo chuỗi được gọi là nhiều, nhiều lần. Điều này chủ yếu là vì tôi lười biếng, chúng tôi chỉ cần các chuỗi được khởi tạo cho một lần chúng tôi thực sự gặp phải điều gì đó mà chúng tôi muốn báo cáo trong đệ quy, nhưng sẽ dễ dàng hơn để khởi tạo mỗi khi có thể gặp phải điều gì đó. Tôi đề cập đến điều này bởi vì những gì bạn sẽ thấy tiếp theo hiển thị nhiều lần khởi tạo, nhưng chỉ một trong số chúng (có lẽ là địa chỉ có địa chỉ <0x1400000001>) đang được sử dụng.

Tôi không thể đảm bảo rằng những thứ tôi hiển thị ở đây có liên quan trực tiếp đến yếu tố gây ra segfault (mặc dù đó là cùng một địa chỉ bất hợp pháp), nhưng như @ nate-eldredge đã hỏi rằng nó không phải là yếu tố mảng khởi tạo ngay trước khi trả về hoặc ngay sau khi trả về trong chức năng gọi. Lưu ý chức năng gọi điện đang khởi tạo 8 trong số này và tôi hiển thị tất cả, với tất cả chúng chứa đầy rác hoặc bộ nhớ không thể truy cập.

nhập mô tả hình ảnh ở đây

CẬP NHẬT 3 , tháo gỡ chức năng trong câu hỏi:

Breakpoint 1, ALIKEC_res_strings_init () at alike.c:75
75    return res;
(gdb) p res.current[0]
$1 = 0x7ffff46a0aa5 "%s%s%s%s"
(gdb) p res.current[1]
$2 = 0x1400000001 <error: Cannot access memory at address 0x1400000001>
(gdb) disas /m ALIKEC_res_strings_init
Dump of assembler code for function ALIKEC_res_strings_init:
53  struct ALIKEC_res_strings ALIKEC_res_strings_init() {
   0x00007ffff4687fc0 <+0>: endbr64 

54    struct ALIKEC_res_strings res;

55  
56    res.target = (const char **) R_alloc(5, sizeof(const char *));
   0x00007ffff4687fc4 <+4>: push   %r12
   0x00007ffff4687fc6 <+6>: mov    $0x8,%esi
   0x00007ffff4687fcb <+11>:    mov    %rdi,%r12
   0x00007ffff4687fce <+14>:    push   %rbx
   0x00007ffff4687fcf <+15>:    mov    $0x5,%edi
   0x00007ffff4687fd4 <+20>:    sub    $0x8,%rsp
   0x00007ffff4687fd8 <+24>:    callq  0x7ffff4687180 <R_alloc@plt>
   0x00007ffff4687fdd <+29>:    mov    $0x8,%esi
   0x00007ffff4687fe2 <+34>:    mov    $0x5,%edi
   0x00007ffff4687fe7 <+39>:    mov    %rax,%rbx

57    res.current = (const char **) R_alloc(5, sizeof(const char *));
   0x00007ffff4687fea <+42>:    callq  0x7ffff4687180 <R_alloc@plt>

58  
59    res.target[0] = "%s%s%s%s";
   0x00007ffff4687fef <+47>:    lea    0x1764a(%rip),%rdx        # 0x7ffff469f640
   0x00007ffff4687ff6 <+54>:    lea    0x18aa8(%rip),%rcx        # 0x7ffff46a0aa5
   0x00007ffff4687ffd <+61>:    mov    %rcx,(%rbx)

60    res.target[1] = "";

61    res.target[2] = "";
   0x00007ffff4688000 <+64>:    mov    %rdx,0x10(%rbx)

62    res.target[3] = "";
   0x00007ffff4688004 <+68>:    mov    %rdx,0x18(%rbx)

63    res.target[4] = "";
   0x00007ffff4688008 <+72>:    mov    %rdx,0x20(%rbx)

64  
65    res.tar_pre = "be";

66  
67    res.current[0] = "%s%s%s%s";
   0x00007ffff468800c <+76>:    mov    %rax,0x8(%r12)
   0x00007ffff4688011 <+81>:    mov    %rcx,(%rax)

68    res.current[1] = "";

69    res.current[2] = "";
   0x00007ffff4688014 <+84>:    mov    %rdx,0x10(%rax)

70    res.current[3] = "";
   0x00007ffff4688018 <+88>:    mov    %rdx,0x18(%rax)

71    res.current[4] = "";
   0x00007ffff468801c <+92>:    mov    %rdx,0x20(%rax)

72  
73    res.cur_pre = "is";

74  
75    return res;
=> 0x00007ffff4688020 <+96>:    lea    0x14fe0(%rip),%rax        # 0x7ffff469d007
   0x00007ffff4688027 <+103>:   mov    %rax,0x10(%r12)
   0x00007ffff468802c <+108>:   lea    0x14fcd(%rip),%rax        # 0x7ffff469d000
   0x00007ffff4688033 <+115>:   mov    %rbx,(%r12)
   0x00007ffff4688037 <+119>:   mov    %rax,0x18(%r12)
   0x00007ffff468803c <+124>:   add    $0x8,%rsp
   0x00007ffff4688040 <+128>:   pop    %rbx
   0x00007ffff4688041 <+129>:   mov    %r12,%rax
   0x00007ffff4688044 <+132>:   pop    %r12
   0x00007ffff4688046 <+134>:   retq   
   0x00007ffff4688047:  nopw   0x0(%rax,%rax,1)

End of assembler dump.

CẬP NHẬT 4 :

Vì vậy, cố gắng phân tích thông qua tiêu chuẩn ở đây là những phần có vẻ phù hợp ( dự thảo C11 ):

6.3.2.3 Chuyển đổi Par7> Toán tử khác> Con trỏ

Một con trỏ tới một loại đối tượng có thể được chuyển đổi thành một con trỏ thành một loại đối tượng khác. Nếu con trỏ kết quả không được căn chỉnh chính xác 68) cho loại được tham chiếu, hành vi không được xác định.
Mặt khác, khi được chuyển đổi trở lại một lần nữa, kết quả sẽ so sánh bằng với con trỏ ban đầu. Khi một con trỏ tới một đối tượng được chuyển đổi thành một con trỏ thành một kiểu ký tự, kết quả sẽ trỏ đến byte có địa chỉ thấp nhất của đối tượng. Gia tăng liên tiếp của kết quả, cho đến kích thước của đối tượng, mang lại con trỏ tới các byte còn lại của đối tượng.

6.5 Biểu thức Par6

Loại hiệu quả của một đối tượng để truy cập vào giá trị được lưu trữ của nó là loại khai báo của đối tượng, nếu có. 87) Nếu một giá trị được lưu trữ vào một đối tượng không có kiểu khai báo thông qua một giá trị có loại không phải là kiểu ký tự, thì loại giá trị đó trở thành loại đối tượng hiệu quả cho truy cập đó và cho các lần truy cập tiếp theo không sửa đổi giá trị được lưu trữ. Nếu một giá trị được sao chép vào một đối tượng không có kiểu khai báo sử dụng memcpy hoặc memmove hoặc được sao chép dưới dạng một mảng kiểu ký tự, thì loại hiệu quả của đối tượng được sửa đổi cho truy cập đó và cho các truy cập tiếp theo không sửa đổi giá trị là loại hiệu quả của đối tượng mà từ đó giá trị được sao chép, nếu nó có một. Đối với tất cả các truy cập khác vào một đối tượng không có loại khai báo, loại đối tượng hiệu quả chỉ đơn giản là loại giá trị được sử dụng cho truy cập.

87) Các đối tượng được phân bổ không có loại khai báo.

IIUC R_alloctrả về phần bù vào mallockhối ed được đảm bảo doublecăn chỉnh và kích thước của khối sau phần bù có kích thước được yêu cầu (cũng có phân bổ trước phần bù cho dữ liệu cụ thể R). R_allocphôi mà con trỏ (char *)trở lại.

Mục 6.2.5 Par 29

Một con trỏ đến void sẽ có cùng các yêu cầu biểu diễn và căn chỉnh như một con trỏ tới một kiểu ký tự. 48) Tương tự, các con trỏ tới các phiên bản đủ điều kiện hoặc không đủ tiêu chuẩn của các loại tương thích sẽ có cùng các yêu cầu về đại diện và căn chỉnh. Tất cả các con trỏ đến các loại cấu trúc sẽ có cùng các yêu cầu đại diện và căn chỉnh như nhau.
Tất cả các con trỏ tới các loại kết hợp sẽ có cùng các yêu cầu đại diện và căn chỉnh như nhau.
Con trỏ đến các loại khác không cần phải có cùng yêu cầu đại diện hoặc căn chỉnh.

48) Các yêu cầu về biểu diễn và căn chỉnh giống nhau có nghĩa là ngụ ý các khả năng thay thế cho các chức năng, trả về các giá trị từ các chức năng và các thành viên của các hiệp hội.

Vì vậy, câu hỏi là "chúng ta được phép viết lại các (char *)đến (const char **)và ghi vào nó như là (const char **)". Tôi đọc ở trên là miễn là con trỏ trên các hệ thống mà mã chạy trong có căn chỉnh tương thích với doublecăn chỉnh, thì không sao.

Có phải chúng ta đang vi phạm "răng cưa nghiêm ngặt"? I E:

6,5 mệnh 7

Một đối tượng sẽ có giá trị được lưu trữ chỉ được truy cập bởi một biểu thức giá trị có một trong các loại sau: 88)

- một loại tương thích với loại hiệu quả của đối tượng ...

88) Mục đích của danh sách này là chỉ định những trường hợp trong đó một đối tượng có thể hoặc không thể được đặt bí danh.

Vì vậy, trình biên dịch nên nghĩ loại hiệu quả của đối tượng được trỏ bởi res.target(hoặc res.current) là gì? Có lẽ là loại khai báo (const char **), hoặc điều này thực sự mơ hồ? Tôi cảm thấy rằng nó không chỉ trong trường hợp này bởi vì không có "giá trị" nào khác trong phạm vi truy cập vào cùng một đối tượng.

Tôi sẽ thừa nhận tôi đang vật lộn để rút ra ý nghĩa từ các phần của tiêu chuẩn này.


Nếu chưa được kiểm tra, có thể đáng để xem xét việc tháo gỡ để xem chính xác những gì đang được thực hiện. Và cũng để so sánh sự tháo gỡ giữa các phiên bản gcc.
kaylum

2
Tôi sẽ không thử gây rối với phiên bản trung kế của GCC. Thật vui khi được vui vẻ, nhưng nó được gọi là thân cây vì một lý do. Thật không may, hầu như không thể nói điều gì sai nếu không có (1) có mã và cấu hình chính xác (2) có cùng phiên bản GCC (3) trên cùng một kiến ​​trúc. Tôi khuyên bạn nên kiểm tra xem điều này có tồn tại không khi 10.0.1 chuyển từ thân cây sang ổn định.
Marco Bonelli

1
Thêm một nhận xét: -mtune=nativetối ưu hóa cho CPU cụ thể mà máy của bạn có. Điều đó sẽ khác nhau đối với những người thử nghiệm khác nhau và có thể là một phần của vấn đề. Nếu bạn chạy phần biên dịch với -vbạn, bạn sẽ có thể thấy họ cpu nào trên máy của bạn (ví dụ: -mtune=skylaketrên máy tính của tôi).
Nate Eldredge

1
Vẫn khó để nói từ chạy gỡ lỗi. Việc tháo gỡ nên được kết luận. Bạn không cần trích xuất bất cứ điều gì, chỉ cần tìm tệp .o được tạo khi bạn biên dịch dự án và tháo rời nó. Bạn cũng có thể sử dụng disassemblehướng dẫn bên trong gdb.
Nate Eldredge

5
Dù sao, xin chúc mừng, bạn là một trong số ít người gặp vấn đề thực sự là lỗi trình biên dịch.
Nate Eldredge

Câu trả lời:


22

Tóm tắt: Đây có vẻ là một lỗi trong gcc, liên quan đến tối ưu hóa chuỗi. Một testcase khép kín là dưới đây. Ban đầu có một số nghi ngờ về việc liệu mã này có đúng không, nhưng tôi nghĩ là có.

Tôi đã báo cáo lỗi là PR 93982 . Một bản sửa lỗi được đề xuất đã được cam kết nhưng nó không khắc phục được trong mọi trường hợp, dẫn đến PR 94015 tiếp theo ( liên kết godbolt ).

Bạn sẽ có thể làm việc xung quanh lỗi bằng cách biên dịch với cờ -fno-optimize-strlen.


Tôi đã có thể giảm trường hợp thử nghiệm của bạn xuống ví dụ tối thiểu sau (cũng trên godbolt ):

struct a {
    const char ** target;
};

char* R_alloc(void);

struct a foo(void) {
    struct a res;
    res.target = (const char **) R_alloc();
    res.target[0] = "12345678";
    res.target[1] = "";
    res.target[2] = "";
    res.target[3] = "";
    res.target[4] = "";
    return res;
}

Với thân cây gcc (phiên bản gcc 10.0.1 20200225 (thử nghiệm)) và -O2(tất cả các tùy chọn khác hóa ra là không cần thiết), hội đồng được tạo trên amd64 như sau:

.LC0:
        .string "12345678"
.LC1:
        .string ""
foo:
        subq    $8, %rsp
        call    R_alloc
        movq    $.LC0, (%rax)
        movq    $.LC1, 16(%rax)
        movq    $.LC1, 24(%rax)
        movq    $.LC1, 32(%rax)
        addq    $8, %rsp
        ret

Vì vậy, bạn hoàn toàn đúng khi trình biên dịch không khởi chạy được res.target[1](lưu ý sự vắng mặt dễ thấy của movq $.LC1, 8(%rax)).

Thật thú vị khi chơi với mã và xem những gì ảnh hưởng đến "lỗi". Có lẽ đáng kể, thay đổi kiểu trả về R_allocđể void *làm cho nó biến mất và cung cấp cho bạn đầu ra lắp ráp "chính xác". Có thể ít quan trọng hơn nhưng thú vị hơn, việc thay đổi chuỗi "12345678"thành dài hơn hoặc ngắn hơn cũng khiến nó biến mất.


Thảo luận trước đây, bây giờ đã được giải quyết - mã rõ ràng là hợp pháp.

Câu hỏi tôi có là mã của bạn có thực sự hợp pháp không. Thực tế là bạn lấy char *trả lại R_alloc()và bỏ qua const char **, sau đó lưu trữ const char *có vẻ như nó có thể vi phạm quy tắc răng cưa nghiêm ngặt , vì charconst char *không phải là loại tương thích. Có một ngoại lệ cho phép bạn truy cập bất kỳ đối tượng nào như char(để thực hiện những thứ như memcpy), nhưng đây là cách khác, và theo cách tốt nhất tôi hiểu nó, điều đó không được phép. Nó làm cho mã của bạn tạo ra hành vi không xác định và do đó trình biên dịch có thể làm bất cứ điều gì hợp pháp mà nó muốn.

Nếu đúng như vậy, cách khắc phục chính xác sẽ là R thay đổi mã của họ để R_alloc()trả về void *thay vì char *. Sau đó, sẽ không có vấn đề răng cưa. Thật không may, mã đó nằm ngoài tầm kiểm soát của bạn và tôi không rõ bạn có thể sử dụng chức năng này như thế nào mà không vi phạm bí danh nghiêm ngặt. Một cách giải quyết khác có thể là xen vào một biến tạm thời, ví dụ như void *tmp = R_alloc(); res.target = tmp;giải quyết vấn đề trong trường hợp thử nghiệm, nhưng tôi vẫn không chắc liệu nó có hợp pháp không.

Tuy nhiên, tôi không chắc chắn về giả thuyết "răng cưa nghiêm ngặt" này, bởi vì việc biên dịch với -fno-strict-aliasing, mà AFAIK được cho là làm cho gcc cho phép các cấu trúc như vậy, không làm cho vấn đề biến mất!


Cập nhật. Thử một số tùy chọn khác nhau, tôi thấy rằng một trong hai -fno-optimize-strlenhoặc -fno-tree-forwpropsẽ dẫn đến mã "chính xác" được tạo. Ngoài ra, sử dụng -O1 -foptimize-strlenmang lại mã không chính xác (nhưng -O1 -ftree-forwpropkhông).

Sau một git bisectbài tập nhỏ , lỗi dường như đã được đưa ra trong cam kết 34fcf41e30ff56155e996f5e04 .


Cập nhật 2. Tôi đã thử đào sâu vào nguồn gcc một chút, chỉ để xem những gì tôi có thể học. (Tôi không tự nhận là bất kỳ chuyên gia biên dịch nào!)

Có vẻ như mã trong tree-ssa-strlen.ccó nghĩa là để theo dõi các chuỗi xuất hiện trong chương trình. Gần như tôi có thể nói, lỗi là khi nhìn vào câu lệnh res.target[0] = "12345678";, trình biên dịch sẽ kết hợp địa chỉ của chuỗi ký tự "12345678"với chính chuỗi đó. (Đó dường như có liên quan đến mã này đáng ngờ đã được thêm vào trong nói trên cam kết, nơi mà nếu nó cố gắng để đếm số byte của một "chuỗi" mà thực sự là một địa chỉ, nó thay vì nhìn vào những gì mà điểm địa chỉ tới.)

Vì vậy, nó nghĩ rằng tuyên bố res.target[0] = "12345678", thay vì lưu trữ các địa chỉ của "12345678"tại địa chỉ res.target, được lưu trữ các chuỗi tự tại địa chỉ đó, như thể báo cáo kết quả đã strcpy(res.target, "12345678"). Lưu ý cho những gì phía trước rằng điều này sẽ dẫn đến việc nul trailing được lưu trữ tại địa chỉ res.target+8(ở giai đoạn này trong trình biên dịch, tất cả các offset đều tính bằng byte).

Bây giờ khi trình biên dịch nhìn vào res.target[1] = "", nó cũng xử lý điều này như thể nó là strcpy(res.target+8, "")8, đến từ kích thước của a char *. Đó là, như thể nó chỉ đơn giản là lưu trữ một byte nul tại địa chỉ res.target+8. Tuy nhiên, trình biên dịch "biết" rằng câu lệnh trước đã lưu trữ một byte nul tại chính địa chỉ đó! Như vậy, tuyên bố này là "dư thừa" và có thể được loại bỏ ( ở đây ).

Điều này giải thích tại sao chuỗi phải dài chính xác 8 ký tự để kích hoạt lỗi. (Mặc dù các bội số khác của 8 cũng có thể kích hoạt lỗi trong các tình huống khác.)


FWIW gọi lại cho một loại con trỏ khác được ghi lại . Tôi không biết về răng cưa để biết liệu có ổn không khi làm lại int*nhưng không const char**.
BrodieG

Nếu sự hiểu biết của tôi về răng cưa nghiêm ngặt là chính xác, thì việc chọn diễn viên int *cũng là bất hợp pháp (hay đúng hơn, thực sự lưu trữ ints là bất hợp pháp).
Nate Eldredge

1
Điều này không có gì để làm với quy tắc răng cưa nghiêm ngặt. Quy tắc răng cưa nghiêm ngặt là về việc truy cập dữ liệu mà bạn đã lưu trữ bằng cách sử dụng các thẻ điều khiển khác nhau. Như bạn chỉ gán ở đây, nó không chạm vào quy tắc răng cưa nghiêm ngặt. Đúc con trỏ là hợp lệ khi cả hai loại con trỏ có cùng yêu cầu căn chỉnh, nhưng ở đây bạn đang truyền char*và làm việc trên x86_64 ... Tôi thấy không có UB ở đây, đây là lỗi gcc.
KamilCuk

1
Có và không, @KamilCuk. Trong thuật ngữ của tiêu chuẩn, "truy cập" bao gồm cả việc đọc và sửa đổi giá trị của một đối tượng. Do đó, quy tắc răng cưa nghiêm ngặt sẽ nói đến "lưu trữ". Nó không bị giới hạn trong các hoạt động đọc lại. Nhưng đối với các đối tượng không có kiểu khai báo, điều đó được đưa ra bởi thực tế là việc viết cho một đối tượng như vậy sẽ tự động thay đổi loại hiệu quả của nó để tương ứng với những gì đã được viết. Các đối tượng không có kiểu khai báo chính xác là các đối tượng được phân bổ động (bất kể loại con trỏ mà chúng được truy cập), vì vậy thực sự không có vi phạm SA ở đây.
John Bollinger

2
Có, @Nate, với định nghĩa đó R_alloc(), chương trình phù hợp, bất kể đơn vị dịch thuật nào R_alloc()được xác định. Đây là trình biên dịch không phù hợp ở đây.
John Bollinger
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.