Yes, it will be different in different software. For R the best way to understand is to experiment and dig under the hood:
r <- setExtent(raster(matrix(1:12, 3, 4)), extent(0, 4, 0, 3))
extract(r, cbind(4, 3))
extract(r, cbind(3, 3))
extract(r, cbind(2.99, 3))
It also changes over time while software is developed, I'm pretty sure that the edge value at 4,3 used to return NA (for example).
The crux difference will usually be about whether the cell is treated as a discrete unit with exact extents (like here, and like in GDAL's default), or as a centre point with no exacting boundary, where the raster is treated like a field varying continuously.
You can see a raster treated this way in R though, try
extract(..., method = "bilinear") in the examples above. It depends what the operation is, and you'll find this also varies in other software. It's actually not clearly defined if you look at the wider variety of conventions in how data is dealt with, though this "cell as rectangle" convention is pretty solid in GDAL and R, it falls down when encountering formats that store each axis-pixel's coordinate (which is redundant) or when cell-edge or cell-centre alignment is ambiguous, or just incorrect. It happens often in the more easily-created formats like NetCDF.
To see how 'raster' does it, run down the rabbit hole (type each line, it will lead you to type the next line in turn):
raster:::.doCellFromXY ... whoops now it's C++
You can do it yourself using arithmetic with coordinates compared to the x and y axis positions - it's worth doing it from scratch for yourself - the key is what cell would the difference in my position from the start edge put me in, and what row you are in (or col depending on how you orient it). You'll find similar code at the bottom of every software implementation, with variations on how they store the "transform", basically the information that encodes the extent, number of pixels, and the size of each pixel.